Repository: joshbirnholz/JBCalendarDatePicker
Branch: master
Commit: acc2e63aebd4
Files: 22
Total size: 79.7 KB
Directory structure:
gitextract_jmv4cs2m/
├── .gitignore
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── JBCalendarDatePicker.xcscheme
├── JBCalendarDatePicker.podspec
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── Info.plist
│ └── JBCalendarDatePicker/
│ ├── CalendarDatePickerViewController.h
│ ├── DateInputView.swift
│ ├── Day.swift
│ ├── JBCalendarDateCell.swift
│ ├── JBCalendarDateCell.xib
│ ├── JBCalendarDatePicker.h
│ ├── JBCalendarViewController.swift
│ ├── JBCalendarViewController.xib
│ ├── JBDatePicker.swift
│ ├── JBDatePickerViewController.swift
│ ├── JBDatePickerViewController.xib
│ └── UIColor+SystemAccent.swift
└── Tests/
└── JBCalendarDatePickerTests/
├── JBCalendarDatePickerTests.swift
└── XCTestManifests.swift
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/JBCalendarDatePicker.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1230"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "JBCalendarDatePicker_JBCalendarDatePicker"
BuildableName = "JBCalendarDatePicker_JBCalendarDatePicker"
BlueprintName = "JBCalendarDatePicker_JBCalendarDatePicker"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "JBCalendarDatePicker"
BuildableName = "JBCalendarDatePicker"
BlueprintName = "JBCalendarDatePicker"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "JBCalendarDatePickerTests"
BuildableName = "JBCalendarDatePickerTests"
BlueprintName = "JBCalendarDatePickerTests"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "JBCalendarDatePickerTests"
BuildableName = "JBCalendarDatePickerTests"
BlueprintName = "JBCalendarDatePickerTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "JBCalendarDatePicker_JBCalendarDatePicker"
BuildableName = "JBCalendarDatePicker_JBCalendarDatePicker"
BlueprintName = "JBCalendarDatePicker_JBCalendarDatePicker"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
================================================
FILE: JBCalendarDatePicker.podspec
================================================
Pod::Spec.new do |s|
# 1
s.platform = :ios
s.ios.deployment_target = '13.0'
s.name = "JBCalendarDatePicker"
s.summary = "A replacement for UIDatePicker made for Catalyst."
s.requires_arc = true
# 2
s.version = "0.2.3"
# 3
s.license = { :type => "MIT", :file => "LICENSE" }
# 4 - Replace with your name and e-mail address
s.author = { "Josh Birnholz" => "josh@birnholz.com" }
# 5 - Replace this URL with your own GitHub page's URL (from the address bar)
s.homepage = "https://github.com/joshbirnholz/JBCalendarDatePicker"
# 6 - Replace this URL with your own Git URL from "Quick Setup"
s.source = { :git => "https://github.com/joshbirnholz/JBCalendarDatePicker.git",
:tag => "#{s.version}" }
# 7
s.framework = "UIKit"
# 8
s.source_files = "JBCalendarDatePicker/**/*.{swift}"
# 9
s.resources = "JBCalendarDatePicker/**/*.{xib}"
# 10
s.swift_version = "5"
end
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Joshua Birnholz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.swift
================================================
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "JBCalendarDatePicker",
platforms: [
.iOS(.v13),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "JBCalendarDatePicker",
targets: ["JBCalendarDatePicker"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "JBCalendarDatePicker",
dependencies: [],
path: "Sources",
exclude: ["Info.plist"],
resources: [
.process("JBCalendarDatePicker/JBDatePickerViewController.xib"),
.process("JBCalendarDatePicker/JBCalendarViewController.xib"),
.process("JBCalendarDatePicker/JBCalendarDateCell.xib"),
]
),
.testTarget(
name: "JBCalendarDatePickerTests",
dependencies: ["JBCalendarDatePicker"],
path: "Tests"
),
],
swiftLanguageVersions: [.v5]
)
================================================
FILE: README.md
================================================
# JBCalendarDatePicker
A replacement for UIDatePicker made for Catalyst.
This is still a work in progress, there are bugs, and although it's written to work with different calendar systems and locales, it's not guaranteed to work correctly with everything!

## Installation
To install as SPM, Go to:
`Xcode -> File -> Swift Packages -> Add Package Dependency`
Then enter this URL:
`https://github.com/mohitnandwani/JBCalendarDatePicker.git`
To install, add the source to the top of your podfile:
`source 'https://github.com/joshbirnholz/JBPodSpecs.git'`
Then add this pod to your targets:
`pod 'JBCalendarDatePicker'`
## Use
There are two classes you can use: `JBDatePickerViewController` and `JBCalendarViewController`.
They are both similar to `UIDatePicker`, and their `date`, `minimumDate`, `maximumDate`, `calendar`, and `locale` properties can be configured in the same way. Configure them before presenting either of the view controllers.
`JBDatePickerViewController` also has a `datePickerMode` property, although `UIDatePicker.Mode.countDownTimer` is not supported.
### JBDatePickerViewController

`JBDatePickerViewController` displays labels showing its represented date and allows the user to use the keyboard to enter a date. When the user clicks on the date portion, the view controller presents its own `JBCalendarViewController`. You can allow the user to select a date, time, or both, by setting the `datePickerMode` property.
```Swift
import JBCalendarDatePicker
class ViewController: UIViewController {
var datePicker: JBDatePickerViewController!
override func viewDidLoad() {
super.viewDidLoad()
let datePicker = JBDatePickerViewController()
view.addSubview(datePicker.view)
addChild(datePicker)
datePicker.didMove(toParent: self)
self.datePicker = datePicker
// Configure the datePicker's properties
}
}
```
Or use it from a storyboard. Drag a Container View onto your storyboard. Change the view controller's class to `JBDatePickerViewController`. Give the embed segue an identifier, and then capture a reference to it:
```Swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Embed Date Picker", let destination = segue.destination as? JBDatePickerViewController {
self.datePicker = destination
// Configure the datePicker's properties
}
}
```
### JBCalendarViewController

`JBCalendarViewController` is just the calendar, without the labels.
The view controller tries to present itself as a popover automatically, so be sure to set the `popoverPresentationController`'s `barButtonItem` property or the `sourceView` and `sourceRect` properties.
```Swift
@IBOutlet func buttonPressed(_ sender: UIBarButtonItem) {
let calendarPicker = JBCalendarViewController()
calendarPicker.popoverPresentationController?.barButtonItem = sender
// Configure the calendar's properties
present(calendarPicker, animated: true, completion: nil)
}
```
There is also a `JBCalendarViewControllerDelegate` protocol.
```Swift
public protocol JBCalendarViewControllerDelegate: class {
func calendarViewControllerDateChanged(_ calendarViewController: JBCalendarViewController)
func calendarViewControllerWillDismiss(_ calendarViewController: JBCalendarViewController)
func calendarViewControllerDidDismiss(_ calendarViewController: JBCalendarViewController)
}
```
================================================
FILE: Sources/Info.plist
================================================
<?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>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
</plist>
================================================
FILE: Sources/JBCalendarDatePicker/CalendarDatePickerViewController.h
================================================
//
// CalendarDatePickerViewController.h
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 28/10/2019.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for CalendarDatePickerViewController.
FOUNDATION_EXPORT double CalendarDatePickerViewControllerVersionNumber;
//! Project version string for CalendarDatePickerViewController.
FOUNDATION_EXPORT const unsigned char CalendarDatePickerViewControllerVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <CalendarDatePickerViewController/PublicHeader.h>
================================================
FILE: Sources/JBCalendarDatePicker/DateInputView.swift
================================================
//
// DateInputView.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 10/29/19.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
protocol DateInputViewDelegate: UIResponder, UIKeyInput {
}
class DateInputView: UIView, UIKeyInput {
weak var delegate: DateInputViewDelegate?
override func becomeFirstResponder() -> Bool {
print(type(of: self), #function)
let value = super.becomeFirstResponder()
print(type(of: self), "is first responder: \(self.isFirstResponder)")
return value
}
override func resignFirstResponder() -> Bool {
print(type(of: self), #function)
return delegate?.resignFirstResponder() ?? super.resignFirstResponder()
}
override var canBecomeFirstResponder: Bool {
return true
}
// MARK: UIKeyInput
var hasText: Bool {
return delegate?.hasText ?? false
}
func insertText(_ text: String) {
delegate?.insertText(text)
}
func deleteBackward() {
delegate?.deleteBackward()
}
// MARK: UITextInputTraits
// this doesn't seem to work for some reason.
private var keyboardType: UIKeyboardType {
return .numberPad
}
}
#endif
================================================
FILE: Sources/JBCalendarDatePicker/Day.swift
================================================
//
// Day.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 28/10/2019.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
import Foundation
struct Day: Equatable, Hashable {
var calendar: Calendar
var day: Int
var month: Int
var year: Int
var date: Date {
DateComponents(calendar: calendar, year: year, month: month, day: day).date!
}
var isToday: Bool {
let todayComponents = calendar.dateComponents([.year, .month, .day], from: Date())
var components = DateComponents(calendar: calendar, year: year, month: month, day: day)
let date = calendar.date(from: components)!
components = calendar.dateComponents([.year, .month, .day], from: date)
return todayComponents.day == components.day && todayComponents.month == components.month && todayComponents.year == components.year
}
static func == (lhs: Day, rhs: Day) -> Bool {
if lhs.day == rhs.day && lhs.month == rhs.month && lhs.year == rhs.year {
return true
}
return lhs.date == rhs.date
}
}
================================================
FILE: Sources/JBCalendarDatePicker/JBCalendarDateCell.swift
================================================
//
// CalendarDateCollectionViewCell.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 28/10/2019.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
class JBCalendarDateCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
}
#endif
================================================
FILE: Sources/JBCalendarDatePicker/JBCalendarDateCell.xib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JBCalendarDateCell" customModule="CalendarDatePickerViewController" customModuleProvider="target"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="JBCalendarDateCell" customModule="JBCalendarDatePicker">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="31" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ucl-T0-oTc">
<rect key="frame" x="4" y="48" width="406" height="810"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Ucl-T0-oTc" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="4" id="Gb8-nW-Kyz"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="Ucl-T0-oTc" secondAttribute="trailing" constant="4" id="aSb-bC-zIs"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="Ucl-T0-oTc" secondAttribute="bottom" constant="4" id="gmd-5m-i2m"/>
<constraint firstItem="Ucl-T0-oTc" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="4" id="xd2-do-aau"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<connections>
<outlet property="label" destination="Ucl-T0-oTc" id="iXA-dA-E4K"/>
</connections>
<point key="canvasLocation" x="139" y="118"/>
</view>
</objects>
</document>
================================================
FILE: Sources/JBCalendarDatePicker/JBCalendarDatePicker.h
================================================
//
// JBDatePicker.h
// JBDatePicker
//
// Created by Josh Birnholz on 10/30/19.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#import <Foundation/Foundation.h>
//! Project version number for JBDatePicker.
FOUNDATION_EXPORT double JBDatePickerVersionNumber;
//! Project version string for JBDatePicker.
FOUNDATION_EXPORT const unsigned char JBDatePickerVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <JBDatePicker/PublicHeader.h>
================================================
FILE: Sources/JBCalendarDatePicker/JBCalendarViewController.swift
================================================
//
// CalendarDatePickerViewController.swift
// Calendar Picker
//
// Created by Josh Birnholz on 10/27/19.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
@objc public protocol JBCalendarViewControllerDelegate: class {
func calendarViewControllerDateChanged(_ calendarViewController: JBCalendarViewController)
func calendarViewControllerWillDismiss(_ calendarViewController: JBCalendarViewController)
func calendarViewControllerDidDismiss(_ calendarViewController: JBCalendarViewController)
}
public class JBCalendarViewController: UIViewController, JBDatePicker {
@objc public weak var delegate: JBCalendarViewControllerDelegate?
@IBOutlet private weak var monthLabel: UILabel!
@IBOutlet private weak var collectionView: UICollectionView!
@IBOutlet private var weekSymbolLabels: [UILabel]!
/// This property always returns `UIDatePicker.Mode.date`. Setting this property to a new value does nothing. It is not possible to change the date picker mode of the calendar interface.
@objc public var datePickerMode: UIDatePicker.Mode {
get {
return .date
}
set {
}
}
@objc public var calendar: Calendar! = Calendar.current {
didSet {
if calendar == nil {
calendar = .current
}
updateWeekLabels()
}
}
@objc public var locale: Locale? = .current {
didSet {
calendar.locale = locale
}
}
@objc public var date: Date = Date() {
didSet {
switch (minimumDate, maximumDate) {
case(let minimumDate?, let maximumDate?) where minimumDate < maximumDate :
date = min(max(date, minimumDate), maximumDate)
case (let minimumDate?, nil):
date = max(date, minimumDate)
case (nil, let maximumDate?):
date = min(date, maximumDate)
default:
break
}
if current != nil {
let components = calendar.dateComponents([.month, .year], from: date)
if components.month! != current.month || components.year! != current.year {
(current.month, current.year) = (components.month!, components.year!)
} else {
collectionView?.reloadData()
}
}
delegate?.calendarViewControllerDateChanged(self)
}
}
@objc public var minimumDate: Date? {
didSet {
collectionView?.reloadData()
}
}
@objc public var maximumDate: Date? {
didSet {
collectionView?.reloadData()
}
}
private var usableMinimumDate: Date? {
if let minimumDate = minimumDate {
if let maximumDate = maximumDate {
if minimumDate < maximumDate {
return minimumDate
} else {
return nil
}
}
return minimumDate
}
return nil
}
private var usableMaximumDate: Date? {
if let maximumDate = maximumDate {
if let minimumDate = minimumDate {
if minimumDate < maximumDate {
return maximumDate
} else {
return nil
}
}
return maximumDate
}
return nil
}
private var selectedDay: Day {
let components = calendar.dateComponents([.day, .month, .year], from: date)
return Day(calendar: calendar, day: components.day!, month: components.month!, year: components.year!)
}
private struct Current {
var month: Int {
didSet {
let firstOfMonth = DateComponents(calendar: calendar, year: year, month: month, day: 1).date!
let range = calendar.range(of: .month, in: .year, for: firstOfMonth)!
if month > range.last! {
month = range.first!
year += 1
} else if month < range.first! {
month = range.last!
year -= 1
}
}
}
var year: Int
private let calendar: Calendar
init(calendar: Calendar, month: Int, year: Int) {
self.calendar = calendar
self.month = month
self.year = year
}
}
private var current: Current! {
didSet {
updateMonthLabel()
updateDays()
collectionView.reloadData()
}
}
private var days: [Day] = []
public required init?(coder: NSCoder) {
super.init(nibName: "JBCalendarViewController", bundle: Bundle(for: Self.self))
commonInit()
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
commonInit()
}
public init() {
super.init(nibName: "JBCalendarViewController", bundle: Bundle(for: Self.self))
commonInit()
}
private func commonInit() {
modalPresentationStyle = .popover
popoverPresentationController?.delegate = self
preferredContentSize = CGSize(width: 200, height: 210)
}
override public func viewDidLoad() {
super.viewDidLoad()
calendar.locale = self.locale
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UINib(nibName: "JBCalendarDateCell", bundle: Bundle(for: Self.self)), forCellWithReuseIdentifier: "DateCell")
#if targetEnvironment(macCatalyst)
view.tintColor = .systemAccent
#endif
let selectedComponents = calendar.dateComponents([.year, .month], from: date)
current = Current(calendar: calendar, month: selectedComponents.month!, year: selectedComponents.year!)
let pan = UIPanGestureRecognizer(target: self, action: #selector(didPan(toSelectCells:)))
collectionView.addGestureRecognizer(pan)
let prevLong = UILongPressGestureRecognizer(target: self, action: #selector(previousMonthButtonTouchDown(_:)))
prevLong.minimumPressDuration = 0
}
private func updateWeekLabels() {
var symbols = calendar.veryShortStandaloneWeekdaySymbols
if (calendar.locale ?? .current).languageCode == "en" {
symbols = calendar.shortStandaloneWeekdaySymbols.map { String($0.prefix(2)) }
}
guard isViewLoaded else { return }
for (index, symbol) in symbols.enumerated() {
weekSymbolLabels[index].text = symbol
}
}
private func updateDays() {
let components = DateComponents(calendar: calendar, year: current.year, month: current.month)
let date = components.date!
let range = calendar.range(of: .day, in: .month, for: date)!
days = range.map { Day(calendar: calendar, day: $0, month: current.month, year: current.year) }
let startDate = calendar.dateInterval(of: .month, for: date)!.start
let weekday = calendar.component(.weekday, from: startDate)
let firstDay = days.first!
for i in 0 ..< weekday-1 {
var day = firstDay
day.day -= i+1
days.insert(day, at: 0)
}
let lastDay = days.last!
let count = calendar.weekdaySymbols.count * 6
for i in 0 ..< (count-days.count) {
var day = lastDay
day.day += i+1
days.append(day)
}
}
private func updateMonthLabel() {
guard isViewLoaded else { return }
let formatter = DateFormatter()
formatter.locale = calendar.locale
formatter.setLocalizedDateFormatFromTemplate("MMM yyyy")
let components = DateComponents(calendar: calendar, year: current.year, month: current.month)
monthLabel.text = formatter.string(from: components.date!)
}
@IBAction private func previousMonthButtonTouchUp(_ sender: Any) {
timer?.invalidate()
timer = nil
}
@IBAction private func selectedDayButtonPressed(_ sender: Any) {
let components = calendar.dateComponents([.month, .year], from: date)
let month = components.month!
let year = components.year!
current = Current(calendar: calendar, month: month, year: year)
}
@IBAction private func nextMonthButtonTouchUp(_ sender: Any) {
timer?.invalidate()
timer = nil
}
@IBAction private func previousMonthButtonTouchDown(_ sender: Any) {
startRepeatingTimer { [weak self] in
self?.current.month -= 1
}
}
private var timer: Timer?
private func startRepeatingTimer(_ action: @escaping () -> Void) {
action()
timer = Timer(fire: Date().addingTimeInterval(0.5), interval: 0.25, repeats: true) { timer in
action()
}
RunLoop.main.add(timer!, forMode: .common)
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
if let timer = self.timer {
timer.invalidate()
self.timer = Timer(timeInterval: 0.075, repeats: true, block: { timer in
action()
})
RunLoop.main.add(self.timer!, forMode: .common)
}
}
}
@IBAction private func nextMonthButtonTouchDown(_ sender: Any) {
startRepeatingTimer { [weak self] in
self?.current.month += 1
}
}
private var lastPanChangeDate: Date = Date()
@objc private func didPan(toSelectCells panGesture: UIPanGestureRecognizer) {
if panGesture.state == .began {
collectionView.isUserInteractionEnabled = false
} else if panGesture.state == .changed, let indexPath = collectionView.indexPathForItem(at: panGesture.location(in: collectionView)) {
let day = days[indexPath.row]
let date = DateComponents(calendar: calendar, year: current.year, month: current.month, day: day.day).date!
let month = calendar.component(.month, from: date)
if month == current.month || -lastPanChangeDate.timeIntervalSinceNow > 0.8 {
self.collectionView.selectItem(at: indexPath, animated: false, scrollPosition: [])
self.collectionView(collectionView, didSelectItemAt: indexPath)
lastPanChangeDate = Date()
}
} else if panGesture.state == .ended {
collectionView.isUserInteractionEnabled = true
}
}
}
extension JBCalendarViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return days.count
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DateCell", for: indexPath) as! JBCalendarDateCell
let day = days[indexPath.row]
let date = DateComponents(calendar: calendar, year: current.year, month: current.month, day: day.day).date!
let components = calendar.dateComponents([.day, .month], from: date)
cell.label.text = String(components.day!)
cell.layer.cornerRadius = 4
cell.layer.masksToBounds = true
let isSelected = day == selectedDay
let highlightedBackgroundColor: UIColor = day.isToday ? view.tintColor : .systemFill
cell.backgroundColor = isSelected ? highlightedBackgroundColor : nil
if day.isToday {
if isSelected {
cell.label.textColor = .lightLabel
} else {
cell.label.textColor = self.view.tintColor
}
} else if let minimumDate = usableMinimumDate, date < minimumDate {
cell.label.textColor = .quaternaryLabel
} else if let maximumDate = usableMaximumDate, date > maximumDate {
cell.label.textColor = .quaternaryLabel
} else if components.month == current.month {
cell.label.textColor = .label
} else {
cell.label.textColor = .tertiaryLabel
}
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let numberOfItems = collectionView.numberOfItems(inSection: 0)
let spacing = (collectionViewLayout as! UICollectionViewFlowLayout).minimumInteritemSpacing * CGFloat(numberOfItems-1)
let width = (self.collectionView.frame.width - spacing) / CGFloat(calendar.weekdaySymbols.count)
let height = (self.collectionView.frame.height - spacing) / CGFloat(6)
return CGSize(width: width, height: height)
}
public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let day = days[indexPath.row]
var components = calendar.dateComponents([.timeZone, .year, .month, .day, .hour, .minute, .second, .nanosecond], from: self.date)
components.day = day.day
components.month = day.month
components.year = day.year
let newDate = calendar.date(from: components)!
if let minimumDate = usableMinimumDate, newDate < minimumDate {
return
} else if let maximumDate = usableMaximumDate, newDate > maximumDate {
return
}
collectionView.reloadData()
self.date = newDate
}
public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
collectionView.reloadData()
}
}
extension JBCalendarViewController: UIPopoverPresentationControllerDelegate {
public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
public func presentationControllerWillDismiss(_ presentationController: UIPresentationController) {
delegate?.calendarViewControllerWillDismiss(self)
print("final date:", date)
}
public func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
delegate?.calendarViewControllerDidDismiss(self)
}
}
#endif
================================================
FILE: Sources/JBCalendarDatePicker/JBCalendarViewController.xib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JBCalendarViewController" customModule="JBCalendarDatePicker">
<connections>
<outlet property="collectionView" destination="SJT-dF-4Dv" id="Fgp-sQ-slW"/>
<outlet property="monthLabel" destination="XsS-gG-JOn" id="t3u-EQ-qBq"/>
<outlet property="view" destination="iN0-l3-epB" id="H4Q-QN-OzQ"/>
<outletCollection property="weekSymbolLabels" destination="NzH-cg-g4f" collectionClass="NSMutableArray" id="PvR-50-ZTa"/>
<outletCollection property="weekSymbolLabels" destination="Jh7-Xy-ne4" collectionClass="NSMutableArray" id="Adw-v9-yyb"/>
<outletCollection property="weekSymbolLabels" destination="GHT-nG-QP9" collectionClass="NSMutableArray" id="WCv-rU-ebN"/>
<outletCollection property="weekSymbolLabels" destination="BZX-9S-jZP" collectionClass="NSMutableArray" id="aFo-et-a8d"/>
<outletCollection property="weekSymbolLabels" destination="nN9-YF-bv0" collectionClass="NSMutableArray" id="kyZ-tY-HYN"/>
<outletCollection property="weekSymbolLabels" destination="Ecz-Oh-w0S" collectionClass="NSMutableArray" id="sEL-kp-Kwx"/>
<outletCollection property="weekSymbolLabels" destination="ju0-r4-MOj" collectionClass="NSMutableArray" id="zrb-Xp-dvd"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="200" height="220"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="1" translatesAutoresizingMaskIntoConstraints="NO" id="9zH-yi-fyW">
<rect key="frame" x="8" y="52" width="184" height="160"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="P47-Dg-Btf">
<rect key="frame" x="0.0" y="0.0" width="184" height="28"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" text="Oct 2019" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XsS-gG-JOn">
<rect key="frame" x="0.0" y="0.0" width="116" height="28"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="18"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" spacing="-11" translatesAutoresizingMaskIntoConstraints="NO" id="L4a-pK-Onm">
<rect key="frame" x="116" y="0.0" width="68" height="28"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5ZB-PE-x1r">
<rect key="frame" x="0.0" y="0.0" width="30" height="28"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="◀︎"/>
<connections>
<action selector="previousMonthButtonTouchDown:" destination="-1" eventType="touchDown" id="n3F-ID-pNe"/>
<action selector="previousMonthButtonTouchUp:" destination="-1" eventType="touchUpOutside" id="4Cm-wu-jUl"/>
<action selector="previousMonthButtonTouchUp:" destination="-1" eventType="touchUpInside" id="KIQ-1V-UtW"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PXw-Xa-rec">
<rect key="frame" x="19" y="0.0" width="30" height="28"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="●"/>
<connections>
<action selector="selectedDayButtonPressed:" destination="-1" eventType="touchUpInside" id="NeK-ty-u1o"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nba-2C-EMV">
<rect key="frame" x="38" y="0.0" width="30" height="28"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<state key="normal" title="▶︎"/>
<connections>
<action selector="nextMonthButtonTouchDown:" destination="-1" eventType="touchDown" id="zim-hz-d73"/>
<action selector="nextMonthButtonTouchUp:" destination="-1" eventType="touchUpOutside" id="Zcj-e5-rkA"/>
<action selector="nextMonthButtonTouchUp:" destination="-1" eventType="touchUpInside" id="g5l-xx-0Ew"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="6sq-CK-ND2">
<rect key="frame" x="0.0" y="29" width="184" height="16"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Su" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="NzH-cg-g4f">
<rect key="frame" x="0.0" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Mo" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="Jh7-Xy-ne4">
<rect key="frame" x="26.5" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tu" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="GHT-nG-QP9">
<rect key="frame" x="53" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="We" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="BZX-9S-jZP">
<rect key="frame" x="79.5" y="0.0" width="25" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Th" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="nN9-YF-bv0">
<rect key="frame" x="106.5" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Fr" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="Ecz-Oh-w0S">
<rect key="frame" x="133" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sa" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.10000000149011612" translatesAutoresizingMaskIntoConstraints="NO" id="ju0-r4-MOj">
<rect key="frame" x="159.5" y="0.0" width="24.5" height="16"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="13"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="eop-u5-hdg">
<rect key="frame" x="0.0" y="46" width="184" height="4.5"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8vZ-od-Ehu">
<rect key="frame" x="0.0" y="0.0" width="184" height="3"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstAttribute="height" constant="3" id="duy-FI-4zu"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5YM-lx-cg4">
<rect key="frame" x="0.0" y="3" width="184" height="1.5"/>
<color key="backgroundColor" systemColor="quaternaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.17999999999999999" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="1.3" id="LYl-xp-ZRp"/>
</constraints>
</view>
</subviews>
</stackView>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" prefetchingEnabled="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SJT-dF-4Dv">
<rect key="frame" x="0.0" y="51.5" width="184" height="108.5"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="JWd-Xu-Nj4">
<size key="itemSize" width="26" height="24"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
</collectionView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="9zH-yi-fyW" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="8" id="FoA-L7-RMt"/>
<constraint firstItem="9zH-yi-fyW" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="8" id="QPv-6S-a73"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="9zH-yi-fyW" secondAttribute="bottom" constant="8" id="Rbt-yF-kR7"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="9zH-yi-fyW" secondAttribute="trailing" constant="8" id="dsb-tS-kE2"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<point key="canvasLocation" x="139" y="130"/>
</view>
</objects>
</document>
================================================
FILE: Sources/JBCalendarDatePicker/JBDatePicker.swift
================================================
//
// JBDatePicker.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 10/29/19.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
public protocol JBDatePicker: UIResponder {
var date: Date { get set }
var calendar: Calendar! { get set }
var locale: Locale? { get set }
var minimumDate: Date? { get set }
var maximumDate: Date? { get set }
var datePickerMode: UIDatePicker.Mode { get set }
}
#endif
================================================
FILE: Sources/JBCalendarDatePicker/JBDatePickerViewController.swift
================================================
//
// JBDatePickerViewController.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 28/10/2019.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
public class JBDatePickerViewController: UIViewController, DateInputViewDelegate, JBDatePicker {
// MARK: Public interface
private var keyboardType: UIKeyboardType {
return .numberPad
}
/// Use this property to change the type of information displayed by the date picker. It determines whether the date picker allows selection of a date, a time, or both date and time. The default mode is `UIDatePicker.Mode.dateAndTime`. See `UIDatePicker.Mode` for a list of mode constants.
///
/// Setting this property to `UIDatePicker.Mode.countDownTimer` has no effect; this date picker does not support the countdown timer mode.
@objc public var datePickerMode: UIDatePicker.Mode = .dateAndTime {
didSet {
if datePickerMode == .countDownTimer {
datePickerMode = oldValue
}
}
}
private var dateInputView: DateInputView! {
return (view as! DateInputView)
}
public var calendar: Calendar! = Calendar.current {
didSet {
if calendar == nil {
calendar = .current
}
}
}
@objc public var locale: Locale? = .current {
didSet {
calendar.locale = locale
}
}
@objc public var date: Date = Date() {
didSet {
switch (minimumDate, maximumDate) {
case(let minimumDate?, let maximumDate?) where minimumDate < maximumDate :
date = min(max(date, minimumDate), maximumDate)
case (let minimumDate?, nil):
date = max(date, minimumDate)
case (nil, let maximumDate?):
date = min(date, maximumDate)
default:
break
}
updateLabelText()
setTextInputString("", updatingLabel: false)
print("date set to \(date)")
isPM = (12...23).contains(calendar.component(.hour, from: date))
presentedCalendar?.delegate = nil
presentedCalendar?.date = date
presentedCalendar?.delegate = self
}
}
@objc public var minimumDate: Date? {
didSet {
updateLabelText()
}
}
@objc public var maximumDate: Date? {
didSet {
updateLabelText()
}
}
private var usableMinimumDate: Date? {
if let minimumDate = minimumDate {
if let maximumDate = maximumDate {
if minimumDate < maximumDate {
return minimumDate
} else {
return nil
}
}
return minimumDate
}
return nil
}
private var usableMaximumDate: Date? {
if let maximumDate = maximumDate {
if let minimumDate = minimumDate {
if minimumDate < maximumDate {
return maximumDate
} else {
return nil
}
}
return maximumDate
}
return nil
}
fileprivate var _textInputString: String = ""
fileprivate var textInputString: String { return _textInputString }
fileprivate func setTextInputString(_ newValue: String, updatingLabel: Bool) {
_textInputString = newValue
if updatingLabel, let selectedDatePart = selectedDatePart {
label(for: selectedDatePart).text = _textInputString
}
}
// MARK: Init
public required init?(coder: NSCoder) {
super.init(nibName: "JBDatePickerViewController", bundle: Bundle(for: Self.self))
commonInit()
}
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
commonInit()
}
public init() {
super.init(nibName: "JBDatePickerViewController", bundle: Bundle(for: Self.self))
commonInit()
}
private func commonInit() {
}
@IBOutlet private var labels: [UILabel]!
@IBOutlet private var slashLabels: [UILabel]!
@IBOutlet private weak var fullStackView: UIStackView!
@IBOutlet private weak var datePartsStackView: UIStackView!
@IBOutlet private weak var timePartsStackView: UIStackView!
override public func viewDidLoad() {
super.viewDidLoad()
calendar.locale = locale ?? .current
view.backgroundColor = .clear
dateInputView.delegate = self
#if targetEnvironment(macCatalyst)
view.tintColor = .systemAccent
#endif
isPM = (12...23).contains(calendar.component(.hour, from: date))
datePartsStackView.isHidden = datePickerMode == .time
timePartsStackView.isHidden = datePickerMode == .date
setupTextFields()
updateLabelText()
}
override public var canBecomeFirstResponder: Bool {
return dateInputView.canBecomeFirstResponder
}
override public func becomeFirstResponder() -> Bool {
print(type(of: self), #function)
if selectedDatePart == nil {
selectedDatePart = dateParts.first
}
return dateInputView.becomeFirstResponder()
}
override public func resignFirstResponder() -> Bool {
print(type(of: self), #function)
selectedDatePart = nil
// if let presented = presentedCalendar {
// dismiss(animated: true, completion: nil)
// }
return super.resignFirstResponder()
}
@objc private func tapGestureRecognized(_ sender: UITapGestureRecognizer) {
guard let label = sender.view as? UILabel, let datePart = self.datePart(for: label) else { return }
selectedDatePart = datePart
_ = dateInputView.becomeFirstResponder()
}
private var isPM = false
private enum DatePart: String, CaseIterable {
case day = "dd"
case month = "MM"
case year = "yyyy"
case hour12 = "h"
case hour24 = "HH"
case minute = "mm"
case amPM = "a"
func set(value: Int, of components: inout DateComponents, using calendar: Calendar, isPM: Bool) {
switch self {
case .day:
components.setValue(value, for: .day)
case .month:
// TODO: Set day to last day of month when the date range for the new month doesn't include the old day.
components.setValue(value, for: .month)
case .year:
components.setValue(value, for: .year)
case .hour12:
var value = value
if value == 12 && !isPM {
value = 0
} else if (1...11).contains(value) && isPM {
value += 12
}
components.setValue(value, for: .hour)
case .hour24:
components.setValue(value, for: .hour)
case .minute:
components.setValue(value, for: .minute)
case .amPM:
break
}
}
func maxComponentLength(using calendar: Calendar) -> Int {
if self == .amPM {
return max(calendar.amSymbol.count, calendar.pmSymbol.count)
} else if self == .hour12 {
return 2
}
return rawValue.count
}
}
private var presentedCalendar: JBCalendarViewController? {
return presentedViewController as? JBCalendarViewController
}
private var selectedDatePart: DatePart? {
didSet {
for datePart in visibleDateParts {
let label = self.label(for: datePart)
let isSelected = selectedDatePart == datePart
label.backgroundColor = isSelected ? view.tintColor : nil
label.textColor = isSelected ? .lightLabel : .label
}
self.setTextInputString("", updatingLabel: false)
guard let selectedDatePart = selectedDatePart else {
// presentedCalendar?.dismiss(animated: true, completion: nil)
return
}
if selectedDatePart == .day || selectedDatePart == .month || selectedDatePart == .year && presentedCalendar == nil {
let calendarVC = JBCalendarViewController()
calendarVC.date = date
calendarVC.calendar = calendar
calendarVC.locale = locale
calendarVC.minimumDate = minimumDate
calendarVC.maximumDate = maximumDate
calendarVC.popoverPresentationController?.sourceView = datePartsStackView
calendarVC.popoverPresentationController?.sourceRect = datePartsStackView.frame
// calendarVC.popoverPresentationController?.sourceRect = dayLabel.frame
calendarVC.popoverPresentationController?.permittedArrowDirections = [.up]
calendarVC.popoverPresentationController?.passthroughViews = [fullStackView]
calendarVC.delegate = self
self.present(calendarVC, animated: true, completion: nil)
} else {
// presentedCalendar?.dismiss(animated: true, completion: nil)
}
}
}
private var dateParts: [DatePart]! {
didSet {
amPMLabel.isHidden = !dateParts.contains(.hour12)
}
}
private var yearLabel: UILabel {
let index = dateParts.firstIndex(of: .year)!
return labels[index]
}
private var monthLabel: UILabel {
let index = dateParts.firstIndex(of: .month)!
return labels[index]
}
private var dayLabel: UILabel {
let index = dateParts.firstIndex(of: .day)!
return labels[index]
}
@IBOutlet private weak var hourLabel: UILabel!
@IBOutlet private weak var minuteLabel: UILabel!
@IBOutlet private weak var amPMLabel: UILabel!
private func setupTextFields() {
var allLabels = labels ?? []
allLabels.append(hourLabel)
allLabels.append(minuteLabel)
allLabels.append(amPMLabel)
for label in allLabels {
label.font = UIFont.monospacedDigitSystemFont(ofSize: label.font!.pointSize, weight: .regular)
label.sizeToFit()
NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: label.frame.size.width).isActive = true
label.layer.masksToBounds = true
label.layer.cornerRadius = 4
label.backgroundColor = .clear
label.textColor = .label
let gesture = UITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:)))
label.addGestureRecognizer(gesture)
label.isUserInteractionEnabled = true
}
let template = dateTemplate
let dateFormat = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale ?? .current)!
let components = dateFormat.split(maxSplits: .max, omittingEmptySubsequences: true, whereSeparator: { character -> Bool in
!template.contains(character.lowercased())
})
dateParts = components.compactMap { DatePart(rawValue: String($0)) }
}
private let dateTemplate: String = "MMddyyyyhmma"
private let formatter = DateFormatter()
private var visibleDateParts: [DatePart] {
let allowedDateParts: Set<DatePart> = {
switch datePickerMode {
case .time:
if dateParts.contains(.hour12) {
return [.hour12, .minute, .amPM]
} else {
return [.hour24, .minute]
}
case .date:
return [.year, .month, .day]
default:
var returnValue: Set<DatePart> = [.year, .month, .day]
if dateParts.contains(.hour12) {
returnValue.insert(.hour12)
returnValue.insert(.minute)
returnValue.insert(.amPM)
} else {
returnValue.insert(.hour24)
returnValue.insert(.minute)
}
return returnValue
}
}()
return dateParts.filter { allowedDateParts.contains($0) }
}
// private var labelsAndDateParts: [(UILabel, DatePart)] {
// return visibleDateParts.map { (self.label(for: $0), $0) }
// }
private func label(for datePart: DatePart) -> UILabel {
switch datePart {
case .day: return dayLabel
case .month: return monthLabel
case .year: return yearLabel
case .hour12: return hourLabel
case .hour24: return hourLabel
case .minute: return minuteLabel
case .amPM: return amPMLabel
}
}
private func datePart(for label: UILabel) -> DatePart? {
switch label {
case yearLabel: return .year
case monthLabel: return .month
case dayLabel: return .day
case hourLabel:
if dateParts.contains(.hour12) {
return .hour12
}
return .hour24
case minuteLabel: return .minute
case amPMLabel: return .amPM
default: return nil
}
}
private func updateLabelText() {
for datePart in visibleDateParts {
formatter.dateFormat = datePart.rawValue
label(for: datePart).text = String(formatter.string(from: date).prefix(datePart.maxComponentLength(using: calendar)))
}
formatter.dateFormat = "a"
amPMLabel.text = formatter.string(from: date)
}
private var finalizeEditTimer: Timer? {
didSet {
oldValue?.invalidate()
}
}
}
extension JBDatePickerViewController: UIKeyInput {
public var hasText: Bool {
return !textInputString.isEmpty
}
fileprivate func selectNextDatePart() {
guard let selectedDatePart = selectedDatePart else { return }
if let index = visibleDateParts.lastIndex(of: selectedDatePart), visibleDateParts.indices.contains(index+1) {
self.selectedDatePart = visibleDateParts[index+1]
} else {
self.selectedDatePart = visibleDateParts.first
}
}
public func insertText(_ text: String) {
guard let selectedDatePart = selectedDatePart else { return }
if text == "\t" {
finalize(datePart: selectedDatePart)
selectNextDatePart()
return
}
if selectedDatePart == .amPM {
setTextInputString(text, updatingLabel: true)
finalize(datePart: selectedDatePart)
return
}
guard let proposedValue = Int(textInputString + text) else { return }
let validValues: [Int] = {
switch selectedDatePart {
case .day:
return calendar.range(of: .day, in: .month, for: date).map(Array.init) ?? []
case .month:
return calendar.range(of: .month, in: .year, for: date).map(Array.init) ?? []
case .year:
return Array(1...9999)
case .hour12:
return Array(1...12)
case .hour24:
return Array(0...23)
case .minute:
return Array(0...59)
case .amPM:
return []
}
}()
let valueIsValid: Bool = {
return validValues.contains(proposedValue)
}()
guard valueIsValid else { return }
setTextInputString(String(proposedValue), updatingLabel: true)
if textInputString.count >= selectedDatePart.maxComponentLength(using: calendar) {
finalize(datePart: selectedDatePart)
} else {
startFinalizeTimer(datePart: selectedDatePart)
}
}
public func deleteBackward() {
guard !textInputString.isEmpty else { return }
var input = textInputString
input.removeLast()
setTextInputString(input, updatingLabel: true)
if let selectedDatePart = selectedDatePart, !textInputString.isEmpty {
startFinalizeTimer(datePart: selectedDatePart)
}
}
private func startFinalizeTimer(datePart: DatePart) {
finalizeEditTimer = Timer(timeInterval: 1, repeats: false) { timer in
self.finalize(datePart: datePart)
}
RunLoop.main.add(finalizeEditTimer!, forMode: .common)
}
private func finalize(datePart: DatePart) {
var components = calendar.dateComponents([.timeZone, .year, .month, .day, .hour, .minute, .second, .nanosecond], from: date)
if let value = Int(textInputString) {
datePart.set(value: value, of: &components, using: calendar, isPM: isPM)
if let date = calendar.date(from: components) {
self.date = date
} else {
updateLabelText()
}
} else if datePart == .amPM && !textInputString.isEmpty {
if calendar.amSymbol.lowercased().hasPrefix(textInputString.lowercased()) && isPM {
isPM = false
print("setting to am")
components.hour! -= 12
} else if calendar.pmSymbol.lowercased().hasPrefix(textInputString.lowercased()) && !isPM {
isPM = true
print("setting to pm")
components.hour! += 12
}
if let date = calendar.date(from: components) {
self.date = date
} else {
updateLabelText()
}
}
setTextInputString("", updatingLabel: false)
finalizeEditTimer?.invalidate()
}
}
extension JBDatePickerViewController: JBCalendarViewControllerDelegate {
public func calendarViewControllerDateChanged(_ calendarViewController: JBCalendarViewController) {
self.date = calendarViewController.date
}
public func calendarViewControllerWillDismiss(_ calendarViewController: JBCalendarViewController) {
_ = dateInputView.resignFirstResponder()
}
public func calendarViewControllerDidDismiss(_ calendarViewController: JBCalendarViewController) {
}
}
#endif
================================================
FILE: Sources/JBCalendarDatePicker/JBDatePickerViewController.xib
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="JBDatePickerViewController" customModule="JBCalendarDatePicker">
<connections>
<outlet property="amPMLabel" destination="Yb6-cX-Iup" id="hi9-tq-aBn"/>
<outlet property="datePartsStackView" destination="1tw-Dr-MZK" id="TQ1-js-BQA"/>
<outlet property="fullStackView" destination="1DW-sK-Kuh" id="swT-4n-yPM"/>
<outlet property="hourLabel" destination="wWz-nR-eEw" id="tlX-VS-R54"/>
<outlet property="minuteLabel" destination="xBH-eh-Kkk" id="ZMz-FT-Biw"/>
<outlet property="timePartsStackView" destination="sGC-mG-oKf" id="WOe-CT-8FY"/>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
<outletCollection property="labels" destination="e10-lT-IGg" collectionClass="NSMutableArray" id="Len-tI-HR5"/>
<outletCollection property="slashLabels" destination="mth-vw-Bki" collectionClass="NSMutableArray" id="A1H-gr-R5g"/>
<outletCollection property="labels" destination="CZQ-vO-Hfs" collectionClass="NSMutableArray" id="ZTh-Hs-4aQ"/>
<outletCollection property="slashLabels" destination="4Ms-VI-PZa" collectionClass="NSMutableArray" id="v71-6n-ufG"/>
<outletCollection property="labels" destination="hgy-52-e1p" collectionClass="NSMutableArray" id="qR2-7b-5tk"/>
<outletCollection property="slashLabels" destination="LWa-FT-OeY" collectionClass="NSMutableArray" id="XKk-Po-Qvl"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT" customClass="DateInputView" customModule="JBCalendarDatePicker">
<rect key="frame" x="0.0" y="0.0" width="439" height="104"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="14" translatesAutoresizingMaskIntoConstraints="NO" id="1DW-sK-Kuh">
<rect key="frame" x="0.0" y="47" width="178.5" height="20.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1tw-Dr-MZK">
<rect key="frame" x="0.0" y="0.0" width="88" height="20.5"/>
<subviews>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="10" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="e10-lT-IGg">
<rect key="frame" x="0.0" y="0.0" width="18" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="/" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mth-vw-Bki">
<rect key="frame" x="18" y="0.0" width="5.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="29" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CZQ-vO-Hfs">
<rect key="frame" x="23.5" y="0.0" width="20.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="/" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Ms-VI-PZa">
<rect key="frame" x="44" y="0.0" width="5.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2019" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hgy-52-e1p">
<rect key="frame" x="49.5" y="0.0" width="38.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="sGC-mG-oKf">
<rect key="frame" x="102" y="0.0" width="76.5" height="20.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KKl-98-e11">
<rect key="frame" x="0.0" y="0.0" width="46.5" height="20.5"/>
<subviews>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="23" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wWz-nR-eEw">
<rect key="frame" x="0.0" y="0.0" width="20.5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=":" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LWa-FT-OeY">
<rect key="frame" x="20.5" y="0.0" width="5" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="59" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xBH-eh-Kkk">
<rect key="frame" x="25.5" y="0.0" width="21" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AM" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Yb6-cX-Iup">
<rect key="frame" x="50.5" y="0.0" width="26" height="20.5"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="1DW-sK-Kuh" firstAttribute="leading" secondItem="fnl-2z-Ty3" secondAttribute="leading" id="KES-GK-c0X"/>
<constraint firstItem="1DW-sK-Kuh" firstAttribute="centerY" secondItem="fnl-2z-Ty3" secondAttribute="centerY" id="kl5-dU-SqA"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<viewLayoutGuide key="safeArea" id="fnl-2z-Ty3"/>
<point key="canvasLocation" x="155.79710144927537" y="-128.57142857142856"/>
</view>
</objects>
</document>
================================================
FILE: Sources/JBCalendarDatePicker/UIColor+SystemAccent.swift
================================================
//
// UIColor+SystemAccent.swift
// CalendarDatePickerViewController
//
// Created by Josh Birnholz on 28/10/2019.
// Copyright © 2019 Josh Birnholz. All rights reserved.
//
#if canImport(UIKit)
import UIKit
extension UIColor {
#if targetEnvironment(macCatalyst)
static var systemAccent: UIColor {
let hasAccentSet = UserDefaults.standard.object(forKey: "AppleAccentColor") != nil
let systemAccentColor = UserDefaults.standard.integer(forKey: "AppleAccentColor")
var returnColor: UIColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.008315349929, green: 0.3450804651, blue: 0.817365706, alpha: 1) : #colorLiteral(red: 0.01329958253, green: 0.3846624196, blue: 0.8779004216, alpha: 1)
}
if hasAccentSet {
switch systemAccentColor {
case -1:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.4039281607, green: 0.403850317, blue: 0.4124818146, alpha: 1) : #colorLiteral(red: 0.5019147992, green: 0.5019902587, blue: 0.5018982291, alpha: 1)
}
case 0:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.82002002, green: 0.2045214176, blue: 0.2204136252, alpha: 1) : #colorLiteral(red: 0.7370213866, green: 0.1443678439, blue: 0.1633504629, alpha: 1)
}
case 1:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.7512640357, green: 0.3605512679, blue: 0.01273573376, alpha: 1) : #colorLiteral(red: 0.8462041616, green: 0.4178547263, blue: 0.05405366421, alpha: 1)
}
case 2:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.8009095192, green: 0.5611655712, blue: 0.05494389683, alpha: 1) : #colorLiteral(red: 0.8690621257, green: 0.6199508309, blue: 0.07889743894, alpha: 1)
}
case 3:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.2549478412, green: 0.5663680434, blue: 0.1645001471, alpha: 1) : #colorLiteral(red: 0.3048421741, green: 0.6298194528, blue: 0.1963118315, alpha: 1)
}
case 5:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.500952661, green: 0.1951716244, blue: 0.5008149147, alpha: 1) : #colorLiteral(red: 0.4900261164, green: 0.1631549001, blue: 0.4976372719, alpha: 1)
}
case 6:
returnColor = UIColor { traitCollection in
traitCollection.userInterfaceStyle == .dark ? #colorLiteral(red: 0.7823504806, green: 0.1956582665, blue: 0.4722630978, alpha: 1) : #colorLiteral(red: 0.8491325974, green: 0.2301979959, blue: 0.5240355134, alpha: 1)
}
default:
break
}
}
return returnColor
}
#endif
static let lightLabel = UIColor { traitCollection in
if traitCollection.userInterfaceStyle == .dark {
return .label
} else {
return .systemBackground
}
}
}
#endif
================================================
FILE: Tests/JBCalendarDatePickerTests/JBCalendarDatePickerTests.swift
================================================
import XCTest
@testable import JBCalendarDatePicker
final class JBCalendarDatePickerTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
}
static var allTests = [
("testExample", testExample),
]
}
================================================
FILE: Tests/JBCalendarDatePickerTests/XCTestManifests.swift
================================================
import XCTest
#if !canImport(ObjectiveC)
public func allTests() -> [XCTestCaseEntry] {
return [
testCase(JBCalendarDatePickerTests.allTests),
]
}
#endif
gitextract_jmv4cs2m/
├── .gitignore
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── JBCalendarDatePicker.xcscheme
├── JBCalendarDatePicker.podspec
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│ ├── Info.plist
│ └── JBCalendarDatePicker/
│ ├── CalendarDatePickerViewController.h
│ ├── DateInputView.swift
│ ├── Day.swift
│ ├── JBCalendarDateCell.swift
│ ├── JBCalendarDateCell.xib
│ ├── JBCalendarDatePicker.h
│ ├── JBCalendarViewController.swift
│ ├── JBCalendarViewController.xib
│ ├── JBDatePicker.swift
│ ├── JBDatePickerViewController.swift
│ ├── JBDatePickerViewController.xib
│ └── UIColor+SystemAccent.swift
└── Tests/
└── JBCalendarDatePickerTests/
├── JBCalendarDatePickerTests.swift
└── XCTestManifests.swift
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (90K chars).
[
{
"path": ".gitignore",
"chars": 53,
"preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n"
},
{
"path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n version = \"1.0\">\n <FileRef\n location = \"self:\">\n </FileRef"
},
{
"path": ".swiftpm/xcode/xcshareddata/xcschemes/JBCalendarDatePicker.xcscheme",
"chars": 4093,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n LastUpgradeVersion = \"1230\"\n version = \"1.3\">\n <BuildAction\n "
},
{
"path": "JBCalendarDatePicker.podspec",
"chars": 881,
"preview": "Pod::Spec.new do |s|\n\n# 1\ns.platform = :ios\ns.ios.deployment_target = '13.0'\ns.name = \"JBCalendarDatePicker\"\ns.summary ="
},
{
"path": "LICENSE",
"chars": 1071,
"preview": "MIT License\n\nCopyright (c) 2019 Joshua Birnholz\n\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "Package.swift",
"chars": 1544,
"preview": "// swift-tools-version:5.3\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
},
{
"path": "README.md",
"chars": 3552,
"preview": "\n# JBCalendarDatePicker\nA replacement for UIDatePicker made for Catalyst.\n\nThis is still a work in progress, there are b"
},
{
"path": "Sources/Info.plist",
"chars": 769,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "Sources/JBCalendarDatePicker/CalendarDatePickerViewController.h",
"chars": 673,
"preview": "//\n// CalendarDatePickerViewController.h\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 28/10/"
},
{
"path": "Sources/JBCalendarDatePicker/DateInputView.swift",
"chars": 1169,
"preview": "//\n// DateInputView.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 10/29/19.\n// Copyrig"
},
{
"path": "Sources/JBCalendarDatePicker/Day.swift",
"chars": 1021,
"preview": "//\n// Day.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 28/10/2019.\n// Copyright © 201"
},
{
"path": "Sources/JBCalendarDatePicker/JBCalendarDateCell.swift",
"chars": 318,
"preview": "//\n// CalendarDateCollectionViewCell.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 28/1"
},
{
"path": "Sources/JBCalendarDatePicker/JBCalendarDateCell.xib",
"chars": 3096,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVe"
},
{
"path": "Sources/JBCalendarDatePicker/JBCalendarDatePicker.h",
"chars": 531,
"preview": "//\n// JBDatePicker.h\n// JBDatePicker\n//\n// Created by Josh Birnholz on 10/30/19.\n// Copyright © 2019 Josh Birnholz. "
},
{
"path": "Sources/JBCalendarDatePicker/JBCalendarViewController.swift",
"chars": 12556,
"preview": "//\n// CalendarDatePickerViewController.swift\n// Calendar Picker\n//\n// Created by Josh Birnholz on 10/27/19.\n// Copyr"
},
{
"path": "Sources/JBCalendarDatePicker/JBCalendarViewController.xib",
"chars": 19090,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVe"
},
{
"path": "Sources/JBCalendarDatePicker/JBDatePicker.swift",
"chars": 479,
"preview": "//\n// JBDatePicker.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 10/29/19.\n// Copyrigh"
},
{
"path": "Sources/JBCalendarDatePicker/JBDatePickerViewController.swift",
"chars": 15442,
"preview": "//\n// JBDatePickerViewController.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 28/10/20"
},
{
"path": "Sources/JBCalendarDatePicker/JBDatePickerViewController.xib",
"chars": 11485,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.XIB\" version=\"3.0\" toolsVe"
},
{
"path": "Sources/JBCalendarDatePicker/UIColor+SystemAccent.swift",
"chars": 3084,
"preview": "//\n// UIColor+SystemAccent.swift\n// CalendarDatePickerViewController\n//\n// Created by Josh Birnholz on 28/10/2019.\n//"
},
{
"path": "Tests/JBCalendarDatePickerTests/JBCalendarDatePickerTests.swift",
"chars": 385,
"preview": "import XCTest\n@testable import JBCalendarDatePicker\n\nfinal class JBCalendarDatePickerTests: XCTestCase {\n func testEx"
},
{
"path": "Tests/JBCalendarDatePickerTests/XCTestManifests.swift",
"chars": 170,
"preview": "import XCTest\n\n#if !canImport(ObjectiveC)\npublic func allTests() -> [XCTestCaseEntry] {\n return [\n testCase(JB"
}
]
About this extraction
This page contains the full source code of the joshbirnholz/JBCalendarDatePicker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (79.7 KB), approximately 21.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.