├── .github └── ISSUE_TEMPLATE ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── .tailor.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── JTAppleCalendar.podspec ├── JTAppleCalendar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── JTAppleCalendar.xcscheme ├── LICENSE ├── Package.swift ├── README.md ├── SampleJTAppleCalendar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── SampleJTAppleCalendar.xcscheme ├── SampleJTAppleCalendar ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── cube.imageset │ │ ├── Contents.json │ │ └── cube.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── Example Calendars │ ├── TestOrientationChanges.swift │ ├── TestPersianCalendar.swift │ ├── TestRangeSelectionViewController.swift │ ├── TestViewController.swift │ ├── TestYearViewViewController.swift │ └── ViewController.swift ├── ExampleDateCells │ ├── DateCellCreatedWithCode │ │ └── CodeCellView.swift │ └── DateCellCreatedWithXIB │ │ ├── CellView.swift │ │ └── CellView.xib ├── ExampleSectionHeaders │ ├── HeaderAsClass │ │ ├── CodePinkSectionHeaderView.swift │ │ └── CodeWhiteSectionHeaderView.swift │ └── HeaderAsXibs │ │ ├── PinkSectionHeaderView.swift │ │ ├── PinkSectionHeaderView.xib │ │ ├── WhiteSectionHeaderView.swift │ │ └── WhiteSectionHeaderView.xib ├── Info.plist ├── Main.storyboard └── SceneDelegate.swift ├── SampleJTAppleCalendarUITests ├── Info.plist └── SampleJTAppleCalendarUITests.swift ├── Sources └── JTAppleCalendar │ ├── CalendarEnums.swift │ ├── CalendarStructs.swift │ ├── GlobalFunctionsAndExtensions.swift │ ├── Info.plist │ ├── JTACCollectionMonthViewDelegates.swift │ ├── JTACCollectionYearViewDelegates.swift │ ├── JTACDayCell.swift │ ├── JTACInteractionMonthFunctions.swift │ ├── JTACInteractionYearFunctions.swift │ ├── JTACMonthActionFunctions.swift │ ├── JTACMonthCell.swift │ ├── JTACMonthDelegateProtocol.swift │ ├── JTACMonthLayout.swift │ ├── JTACMonthLayoutHorizontalCalendar.swift │ ├── JTACMonthLayoutProtocol.swift │ ├── JTACMonthLayoutVerticalCalendar.swift │ ├── JTACMonthQueryFunctions.swift │ ├── JTACMonthReusableView.swift │ ├── JTACMonthView.swift │ ├── JTACMonthViewProtocols.swift │ ├── JTACScrollViewDelegates.swift │ ├── JTACVariables.swift │ ├── JTACYearView.swift │ ├── JTACYearViewProtocols.swift │ ├── JTAppleCalendar.h │ └── PrivacyInfo.xcprivacy ├── Tests ├── JTAppleCalendarTests │ ├── JTAppleCalendarTests.swift │ └── XCTestManifests.swift └── LinuxMain.swift └── docs ├── adding-events ├── Adding Events.md └── images │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ └── image5.png ├── build-calendar ├── Build A Calendar From Scratch.md └── images │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ └── image6.png ├── common-elements ├── Common Elements.md ├── configure-in-out-month-dates │ ├── Configuring inDates monthDates outDates.md │ └── images │ │ ├── image1.png │ │ ├── image2.png │ │ ├── image3.png │ │ └── image4.png ├── device-rotation │ └── Handling Device Rotation.md └── regular-selection │ ├── Regular Selection.md │ └── images │ └── image1.png ├── get-started ├── Get Started.md └── image1.gif ├── headers ├── Headers.md └── images │ ├── image1.png │ ├── image2.gif │ ├── image3.gif │ ├── image4.png │ ├── image5.png │ ├── image6.png │ ├── image7.png │ └── image8.png ├── implementing-week-numbers ├── Implementing week numbers.md └── images │ ├── image1.png │ ├── image2.png │ ├── image3.png │ └── image4.png ├── installation └── Installation.md ├── migration-guide └── v8 Migration Guide.md ├── range-selection-styles ├── Range selection styles.md └── images │ ├── image1.gif │ ├── image2.png │ ├── image3.gif │ └── image4.gif ├── scrolling-modes ├── Scrolling Modes.md └── images │ ├── image1.gif │ ├── image2.gif │ ├── image3.gif │ └── image4.gif └── switch-month-to-week-view ├── Switch between month-view and week-view.md └── images ├── image1.png ├── image2.png ├── image3.png ├── image4.png └── image5.png /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 5 | 6 | **(Required) Version Number:** 7 | 8 | ## Description 9 | 10 | ## Steps To Reproduce 11 | 12 | ## Expected Behavior 13 | 14 | ## Additional Context 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | Carthage 26 | # We recommend against adding the Pods directory to your .gitignore. However 27 | # you should judge for yourself, the pros and cons are mentioned at: 28 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 29 | # 30 | # Note: if you ignore the Pods directory, make sure to uncomment 31 | # `pod install` in .travis.yml 32 | # 33 | # Pods/ 34 | #xcshareddata 35 | /.swiftlint.yml 36 | /.swift-version 37 | /Index 38 | xbcbb2nb 39 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.tailor.yml: -------------------------------------------------------------------------------- 1 | except: 2 | - upper-camel-case 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10 3 | env: 4 | global: 5 | - LC_CTYPE=en_US.UTF-8 6 | - LANG=en_US.UTF-8 7 | - WORKSPACE=JTAppleCalendar.xcworkspace 8 | - IOS_FRAMEWORK_SCHEME="JTAppleCalendar iOSTests" 9 | - TVOS_FRAMEWORK_SCHEME="JTAppleCalendar tvOSTests" 10 | - IOS_SDK=iphonesimulator12.0 11 | - TVOS_SDK=appletvsimulator12.0 12 | - EXAMPLE_SCHEME_IOS="JTAppleCalendar iOS Example" 13 | matrix: 14 | - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" EXAMPLE_SCHEME="$EXAMPLE_SCHEME_IOS" POD_LINT="NO" 15 | - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO" 16 | script: 17 | - set -o pipefail 18 | - xcodebuild -version 19 | - xcodebuild -showsdks 20 | 21 | # Build Framework in Debug and Run Tests if specified 22 | - if [ $RUN_TESTS == "YES" ]; then 23 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty -c; 24 | else 25 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c; 26 | fi 27 | 28 | # Build Example in Debug if specified 29 | - if [ $BUILD_EXAMPLE == "YES" ]; then 30 | xcodebuild -workspace "$WORKSPACE" -scheme "$EXAMPLE_SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c; 31 | fi 32 | 33 | # Run `pod lib lint` if specified 34 | - if [ $POD_LINT == "YES" ]; then 35 | pod lib lint; 36 | fi 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > This document has not been written yet. Please consider submitting a PR to help out 3 | -------------------------------------------------------------------------------- /JTAppleCalendar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "JTAppleCalendar" 3 | s.version = "8.0.5" 4 | s.summary = "The Unofficial Swift Apple Calendar Library. View. Control. for iOS & tvOS" 5 | s.description = <<-DESC 6 | A highly configurable Apple calendar control. Contains features like boundary dates, month and week view. Very light weight. 7 | DESC 8 | 9 | s.homepage = "https://patchthecode.com" 10 | # s.screenshots = "https://patchthecode.github.io/" 11 | s.license = 'MIT' 12 | s.author = { "JayT" => "patchthecode@gmail.com" } 13 | s.source = { :git => "https://github.com/patchthecode/JTAppleCalendar.git", :tag => s.version.to_s } 14 | 15 | s.swift_version = '5' 16 | 17 | s.ios.deployment_target = '11.0' 18 | s.tvos.deployment_target = '11.0' 19 | 20 | s.source_files = 'Sources/JTAppleCalendar/*.swift' 21 | s.resource_bundles = {'JTAppleCalendar' => ['Sources/JTAppleCalendar/PrivacyInfo.xcprivacy']} 22 | end 23 | 24 | -------------------------------------------------------------------------------- /JTAppleCalendar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /JTAppleCalendar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /JTAppleCalendar.xcodeproj/xcshareddata/xcschemes/JTAppleCalendar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 JayT 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "JTAppleCalendar", 8 | platforms: [ 9 | .iOS(.v12), 10 | ], 11 | products: [ 12 | .library( 13 | name: "JTAppleCalendar", 14 | targets: ["JTAppleCalendar"]), 15 | ], 16 | targets: [ 17 | .target( 18 | name: "JTAppleCalendar", 19 | dependencies: [], 20 | resources: [.copy("PrivacyInfo.xcprivacy")]), 21 | .testTarget( 22 | name: "JTAppleCalendarTests", 23 | dependencies: ["JTAppleCalendar"]), 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![jtapplecalendarnewlogo](https://cloud.githubusercontent.com/assets/2439146/20656424/a1c98c8e-b4e1-11e6-9833-5fa6430f5a8c.png)](https://github.com/patchthecode/JTAppleCalendar) 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/JTAppleCalendar.svg?style=flat)](http://cocoapods.org/pods/JTAppleCalendar) [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](https://img.shields.io/cocoapods/p/JTAppleCalendar.svg?style=flat)](http://cocoapods.org/pods/JTAppleCalendar) [![License](https://img.shields.io/cocoapods/l/JTAppleCalendar.svg?style=flat)](http://cocoapods.org/pods/JTAppleCalendar) [![](https://www.paypalobjects.com/webstatic/en_US/btn/btn_donate_74x21.png)](https://github.com/patchthecode/JTAppleCalendar/wiki/Support) [![Backers on Open Collective](https://opencollective.com/JTAppleCalendar/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/JTAppleCalendar/sponsors/badge.svg)](#sponsors) [![Open Source Helpers](https://www.codetriage.com/patchthecode/jtapplecalendar/badges/users.svg)](https://www.codetriage.com/patchthecode/jtapplecalendar) 4 | 5 | #### Q: How will my calendar dateCells look with this library? 6 | 7 | **A**: However you want them to look. 8 | 9 |

10 | 11 | 12 | 13 |

14 |

15 | More Images 16 |

17 | 18 | ## Features 19 | 20 | --- 21 | 22 | - [x] Range selection - select dates in a range. The design is entirely up to you. 23 | - [x] Boundary dates - limit the calendar date range 24 | - [x] [Week/month mode](./docs/switch-month-to-week-view/Switch%20between%20month-view%20and%20week-view.md) - show 1 row of weekdays. Or 2, 3 or 6 25 | - [x] Custom cells - make your day-cells look however you want, with any functionality you want 26 | - [x] Custom calendar view - make your calendar look however you want, with what ever functionality you want 27 | - [x] First Day of week - pick anyday to be first day of the week 28 | - [x] Horizontal or vertical mode 29 | - [x] Ability to add [month headers](./docs/headers/Headers.md) in varying sizes/styles of your liking 30 | - [x] Ability to scroll to any month by simply using the date 31 | - [x] Ability to design your calendar [however you want.](https://github.com/patchthecode/JTAppleCalendar/issues/2) You want it, you build it 32 | 33 | --- 34 | 35 | ## How do I use this library? 36 | 37 | > [!WARNING] 38 | > The wiki currently links to an external site that is down. It is recommended to [view the docs](./docs/get-started/Get%20Started.md) at this time, but be aware they may not be up to date currently 39 | 40 | ### >> [Read the wiki](https://github.com/patchthecode/JTAppleCalendar/wiki/Tutorials) for Tutorials and example code to download or [view the docs](./docs/get-started/Get%20Started.md) 41 | 42 | ## [Version 8.0.0 migration guide](./docs/migration-guide/v8%20Migration%20Guide.md) 43 | 44 | --- 45 | 46 | ## Sponsors 47 | 48 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. 49 | Want to become a sponsor? Send an email to patchthecode@gmail.com 50 | 51 | 52 | 53 | 54 | ## Contributors 55 | 56 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 57 | 58 | 59 | ## Backers 60 | 61 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/JTAppleCalendar#backer)] 62 | 63 | 64 | 65 | ## License 66 | 67 | JTAppleCalendar is available under the MIT license. See the LICENSE file for more info. 68 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar.xcodeproj/xcshareddata/xcschemes/SampleJTAppleCalendar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SampleJTAppleCalendar 4 | // 5 | // Created by Jeron Thomas on 2019-06-25. 6 | // Copyright © 2019 OsTech. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillTerminate(_ application: UIApplication) { 22 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 23 | } 24 | 25 | // MARK: UISceneSession Lifecycle 26 | 27 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 31 | } 32 | 33 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Assets.xcassets/cube.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "cube.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Assets.xcassets/cube.imageset/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/SampleJTAppleCalendar/Assets.xcassets/cube.imageset/cube.png -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Example Calendars/TestOrientationChanges.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestOrientationChanges.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by Jay Thomas on 2017-08-19. 6 | // 7 | 8 | import UIKit 9 | 10 | class TestOrientationChanges: UIViewController { 11 | 12 | override func viewDidLoad() { 13 | super.viewDidLoad() 14 | 15 | // Do any additional setup after loading the view. 16 | } 17 | 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Example Calendars/TestPersianCalendar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestPersianCalendar.swift 3 | // SampleCalendar 4 | // 5 | // Copyright © 2017 Aminous. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import JTAppleCalendar 10 | 11 | class TestPersianCalendar: UIViewController { 12 | 13 | @IBOutlet weak var calendarView: JTACMonthView! 14 | 15 | lazy var dateFormatter: DateFormatter = { 16 | let dateFormatter = DateFormatter() 17 | dateFormatter.timeZone = TimeZone(abbreviation: "UTC") 18 | dateFormatter.dateFormat = "hh:mm" 19 | return dateFormatter 20 | }() 21 | 22 | let persianDateFormatter: DateFormatter = { 23 | let dateFormatter = DateFormatter() 24 | dateFormatter.calendar = Calendar(identifier: .persian) 25 | dateFormatter.dateFormat = "yyyy/MM/dd" 26 | return dateFormatter 27 | }() 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | // Do any additional setup after loading the view, typically from a nib. 32 | setupCalendarView() 33 | calendarView.calendarDataSource = self 34 | calendarView.calendarDelegate = self 35 | } 36 | 37 | override func didReceiveMemoryWarning() { 38 | super.didReceiveMemoryWarning() 39 | // Dispose of any resources that can be recreated. 40 | } 41 | 42 | 43 | func handleCellSelected(cell: JTACDayCell?, cellState: CellState){ 44 | guard let validCell = cell as? CalendarCell else { return } 45 | if cellState.isSelected { 46 | validCell.selectedView.isHidden = false 47 | } else { 48 | validCell.selectedView.isHidden = true 49 | } 50 | } 51 | 52 | func handleCellTextColor(cell: JTACDayCell?, cellState: CellState){ 53 | guard let validCell = cell as? CalendarCell else { return } 54 | if cellState.isSelected { 55 | validCell.dateLabel.textColor = UIColor.white 56 | } else { 57 | let today = Date() 58 | persianDateFormatter.dateFormat = "yyyy MM dd" 59 | let todayDateStr = persianDateFormatter.string(from: today) 60 | dateFormatter.dateFormat = "yyyy MM dd" 61 | let cellDateStr = dateFormatter.string(from: cellState.date) 62 | 63 | if todayDateStr == cellDateStr { 64 | validCell.dateLabel.textColor = UIColor.yellow 65 | } else { 66 | if cellState.dateBelongsTo == .thisMonth { 67 | validCell.dateLabel.textColor = UIColor.white 68 | } else { //i.e. case it belongs to inDate or outDate 69 | validCell.dateLabel.textColor = UIColor.gray 70 | } 71 | } 72 | } 73 | } 74 | 75 | 76 | func setupCalendarView(){ 77 | //Setup calendar spacing 78 | calendarView.minimumLineSpacing = 0 79 | calendarView.minimumInteritemSpacing = 0 80 | 81 | } 82 | } 83 | 84 | extension TestPersianCalendar: JTACMonthViewDataSource { 85 | func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters { 86 | 87 | let persianCalendar = Calendar(identifier: .persian) 88 | 89 | let testFotmatter = DateFormatter() 90 | testFotmatter.dateFormat = "yyyy/MM/dd" 91 | testFotmatter.timeZone = persianCalendar.timeZone 92 | testFotmatter.locale = persianCalendar.locale 93 | 94 | let startDate = testFotmatter.date(from: "2017/01/01")! 95 | let endDate = testFotmatter.date(from: "2017/09/30")! 96 | 97 | 98 | let parameters = ConfigurationParameters(startDate: startDate, endDate: endDate, calendar: persianCalendar) 99 | return parameters 100 | } 101 | } 102 | 103 | extension TestPersianCalendar: JTACMonthViewDelegate { 104 | func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 105 | // The code here is the same as cellForItem function 106 | let cell = cell as! CellView 107 | cell.dayLabel.text = cellState.text 108 | 109 | handleCellSelected(cell: cell, cellState: cellState) 110 | handleCellTextColor(cell: cell, cellState: cellState) 111 | } 112 | 113 | func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell { 114 | let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "mainInfo_CalenderCell", for: indexPath) as! CellView 115 | cell.dayLabel.text = cellState.text 116 | 117 | handleCellSelected(cell: cell, cellState: cellState) 118 | handleCellTextColor(cell: cell, cellState: cellState) 119 | 120 | return cell 121 | } 122 | 123 | 124 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState) { 125 | print(cellState.dateBelongsTo) 126 | handleCellSelected(cell: cell, cellState: cellState) 127 | handleCellTextColor(cell: cell, cellState: cellState) 128 | } 129 | 130 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState) { 131 | handleCellSelected(cell: cell, cellState: cellState) 132 | handleCellTextColor(cell: cell, cellState: cellState) 133 | } 134 | 135 | 136 | } 137 | 138 | class CalendarCell: JTACDayCell { 139 | @IBOutlet weak var dateLabel: UILabel! 140 | @IBOutlet weak var selectedView: UIView! 141 | } 142 | 143 | 144 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Example Calendars/TestRangeSelectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestRangeSelectionViewController.swift 3 | // JTAppleCalendar iOS 4 | // 5 | // Created by Jay Thomas on 2018-08-07. 6 | // 7 | 8 | import UIKit 9 | import JTAppleCalendar 10 | 11 | class TestRangeSelectionViewController: UIViewController { 12 | 13 | @IBOutlet weak var monthLabel: UILabel! 14 | @IBOutlet weak var calendarView: JTACMonthView! 15 | let df = DateFormatter() 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | calendarView.visibleDates() { visibleDates in 21 | self.setupMonthLabel(date: visibleDates.monthDates.first!.date) 22 | } 23 | 24 | calendarView.allowsMultipleSelection = true 25 | calendarView.allowsMultipleSelection = true 26 | 27 | } 28 | 29 | func setupMonthLabel(date: Date) { 30 | df.dateFormat = "MMM" 31 | monthLabel.text = df.string(from: date) 32 | } 33 | 34 | func handleConfiguration(cell: JTACDayCell?, cellState: CellState) { 35 | guard let cell = cell as? TestRangeSelectionViewControllerCell else { return } 36 | handleCellColor(cell: cell, cellState: cellState) 37 | handleCellSelection(cell: cell, cellState: cellState) 38 | } 39 | 40 | func handleCellColor(cell: TestRangeSelectionViewControllerCell, cellState: CellState) { 41 | if cellState.dateBelongsTo == .thisMonth { 42 | cell.label.textColor = .black 43 | } else { 44 | cell.label.textColor = .gray 45 | } 46 | } 47 | 48 | func handleCellSelection(cell: TestRangeSelectionViewControllerCell, cellState: CellState) { 49 | cell.selectedView.isHidden = !cellState.isSelected 50 | 51 | switch cellState.selectedPosition() { 52 | case .left: 53 | cell.selectedView.layer.cornerRadius = 20 54 | cell.selectedView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner] 55 | case .middle: 56 | cell.selectedView.layer.cornerRadius = 0 57 | cell.selectedView.layer.maskedCorners = [] 58 | case .right: 59 | cell.selectedView.layer.cornerRadius = 20 60 | cell.selectedView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner] 61 | case .full: 62 | cell.selectedView.layer.cornerRadius = 20 63 | cell.selectedView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner] 64 | default: break 65 | } 66 | 67 | 68 | 69 | 70 | } 71 | } 72 | 73 | extension TestRangeSelectionViewController: JTACMonthViewDelegate, JTACMonthViewDataSource { 74 | 75 | func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 76 | handleConfiguration(cell: cell, cellState: cellState) 77 | } 78 | 79 | func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell { 80 | let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "cell", for: indexPath) as! TestRangeSelectionViewControllerCell 81 | cell.label.text = cellState.text 82 | self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath) 83 | return cell 84 | } 85 | 86 | func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) { 87 | setupMonthLabel(date: visibleDates.monthDates.first!.date) 88 | } 89 | 90 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) { 91 | handleConfiguration(cell: cell, cellState: cellState) 92 | } 93 | 94 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) { 95 | handleConfiguration(cell: cell, cellState: cellState) 96 | } 97 | 98 | func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters { 99 | let df = DateFormatter() 100 | df.dateFormat = "yyyy MM dd" 101 | 102 | let startDate = df.date(from: "2017 01 01")! 103 | let endDate = df.date(from: "2017 12 31")! 104 | 105 | let parameter = ConfigurationParameters(startDate: startDate, 106 | endDate: endDate, 107 | numberOfRows: 6, 108 | generateInDates: .forAllMonths, 109 | generateOutDates: .tillEndOfGrid, 110 | firstDayOfWeek: .sunday) 111 | return parameter 112 | } 113 | 114 | 115 | } 116 | 117 | 118 | 119 | class TestRangeSelectionViewControllerCell: JTACDayCell { 120 | @IBOutlet weak var label: UILabel! 121 | @IBOutlet weak var selectedView: UIView! 122 | } 123 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Example Calendars/TestViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestViewController.swift 3 | // JTAppleCalendar iOS 4 | // 5 | // Created by Jay Thomas on 2017-07-11. 6 | // 7 | 8 | import UIKit 9 | import JTAppleCalendar 10 | 11 | class TestViewController: UIViewController { 12 | @IBOutlet weak var monthLabel: UILabel! 13 | @IBOutlet var calendarView: JTACMonthView! 14 | @IBOutlet var theView: UIView! 15 | @IBOutlet weak var viewHeightConstraint: NSLayoutConstraint! 16 | let formatter = DateFormatter() 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | self.calendarView.visibleDates {[unowned self] (visibleDates: DateSegmentInfo) in 21 | self.setupViewsOfCalendar(from: visibleDates) 22 | } 23 | 24 | } 25 | @IBAction func selectABunch(_ sender: UIButton) { 26 | formatter.dateFormat = "yyyy MM dd" 27 | let date = formatter.date(from: "2017 01 01")! 28 | let date2 = formatter.date(from: "2017 12 25")! 29 | calendarView.selectDates(from: date, to: date2, triggerSelectionDelegate: true) 30 | } 31 | @IBAction func zeroHeight(_ sender: UIButton) { 32 | let frame = calendarView.frame 33 | calendarView.frame = CGRect(x: frame.origin.x, 34 | y: frame.origin.y, 35 | width: frame.width, 36 | height: 0) 37 | calendarView.reloadData() 38 | } 39 | @IBAction func twoHeight(_ sender: UIButton) { 40 | let frame = calendarView.frame 41 | calendarView.frame = CGRect(x: frame.origin.x, 42 | y: frame.origin.y, 43 | width: frame.width, 44 | height: 50) 45 | calendarView.reloadData() 46 | } 47 | @IBAction func twoHundredHeight(_ sender: UIButton) { 48 | let frame = calendarView.frame 49 | calendarView.frame = CGRect(x: frame.origin.x, 50 | y: frame.origin.y, 51 | width: frame.width, 52 | height: 200) 53 | calendarView.reloadData() 54 | } 55 | 56 | @IBAction func zeroHeightView(_ sender: UIButton) { 57 | viewHeightConstraint.constant = 0 58 | calendarView.reloadData() 59 | 60 | } 61 | @IBAction func twoHeightView(_ sender: UIButton) { 62 | viewHeightConstraint.constant = 50 63 | calendarView.reloadData() 64 | } 65 | @IBAction func twoHundredHeightView(_ sender: UIButton) { 66 | viewHeightConstraint.constant = 200 67 | calendarView.reloadData() 68 | } 69 | 70 | @IBAction func selectOneDate(_ sender: UIButton) { 71 | formatter.dateFormat = "yyyy MM dd" 72 | let date = formatter.date(from: "2017 01 03")! 73 | calendarView.selectDates([date]) 74 | } 75 | @IBAction func selectOtherDate(_ sender: UIButton) { 76 | formatter.dateFormat = "yyyy MM dd" 77 | let date = formatter.date(from: "2017 01 31")! 78 | calendarView.selectDates([date]) 79 | } 80 | 81 | @IBAction func selectOneMonth(_ sender: UIButton) { 82 | formatter.dateFormat = "yyyy MM dd" 83 | let date = formatter.date(from: "2017 02 01")! 84 | calendarView.selectDates([date]) 85 | } 86 | @IBAction func reload(_ sender: UIButton) { 87 | calendarView.reloadData() 88 | } 89 | 90 | @IBAction func debugthis(_ sender: UIButton) { 91 | print(calendarView.selectedDates) 92 | } 93 | @IBAction func singleSelect(_ sender: UIButton) { 94 | calendarView.allowsMultipleSelection = false 95 | calendarView.allowsMultipleSelection = false 96 | } 97 | @IBAction func multiSelect(_ sender: UIButton) { 98 | calendarView.allowsMultipleSelection = true 99 | calendarView.allowsMultipleSelection = true 100 | } 101 | func setupViewsOfCalendar(from visibleDates: DateSegmentInfo) { 102 | guard let startDate = visibleDates.monthDates.first?.date else { 103 | return 104 | } 105 | let month = Calendar.current.dateComponents([.month], from: startDate).month! 106 | let monthName = DateFormatter().monthSymbols[(month-1) % 12] 107 | // 0 indexed array 108 | let year = Calendar.current.component(.year, from: startDate) 109 | monthLabel.text = monthName + " " + String(year) 110 | } 111 | 112 | func configureCell(view: JTACDayCell?, cellState: CellState) { 113 | guard let myCustomCell = view as? CellView else { return } 114 | handleCellTextColor(view: myCustomCell, cellState: cellState) 115 | handleCellSelection(view: myCustomCell, cellState: cellState) 116 | } 117 | 118 | func handleCellSelection(view: CellView, cellState: CellState) { 119 | if calendarView.allowsMultipleSelection { 120 | switch cellState.selectedPosition() { 121 | case .full: view.backgroundColor = .green 122 | case .left: view.backgroundColor = .yellow 123 | case .right: view.backgroundColor = .red 124 | case .middle: view.backgroundColor = .blue 125 | case .none: view.backgroundColor = nil 126 | } 127 | } else { 128 | if cellState.isSelected { 129 | view.backgroundColor = UIColor.red 130 | } else { 131 | view.backgroundColor = UIColor.white 132 | } 133 | } 134 | } 135 | func handleCellTextColor(view: CellView, cellState: CellState) { 136 | if cellState.dateBelongsTo == .thisMonth { 137 | view.dayLabel.textColor = UIColor.black 138 | } else { 139 | view.dayLabel.textColor = UIColor.gray 140 | } 141 | } 142 | 143 | var iii: Date? 144 | } 145 | 146 | 147 | extension TestViewController: JTACMonthViewDataSource, JTACMonthViewDelegate { 148 | func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 149 | configureCell(view: cell, cellState: cellState) 150 | } 151 | 152 | func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell { 153 | let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "cell", for: indexPath) as! CellView 154 | configureCell(view: cell, cellState: cellState) 155 | if cellState.text == "1" { 156 | formatter.dateFormat = "MMM" 157 | let month = formatter.string(from: date) 158 | cell.dayLabel .text = "\(month) \(cellState.text)" 159 | } else { 160 | cell.dayLabel .text = cellState.text 161 | } 162 | return cell 163 | } 164 | 165 | func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters { 166 | formatter.dateFormat = "yyyy MM dd" 167 | formatter.timeZone = Calendar.current.timeZone 168 | formatter.locale = Calendar.current.locale 169 | 170 | 171 | let startDate = formatter.date(from: "2017 01 01")! 172 | let endDate = formatter.date(from: "2030 02 01")! 173 | 174 | let parameters = ConfigurationParameters(startDate: startDate,endDate: endDate) 175 | return parameters 176 | } 177 | 178 | 179 | func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) { 180 | setupViewsOfCalendar(from: visibleDates) 181 | } 182 | 183 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState) { 184 | configureCell(view: cell, cellState: cellState) 185 | } 186 | 187 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState) { 188 | configureCell(view: cell, cellState: cellState) 189 | } 190 | 191 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 192 | calendarView.viewWillTransition(to: size, with: coordinator, anchorDate: iii) 193 | } 194 | 195 | 196 | } 197 | 198 | 199 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Example Calendars/TestYearViewViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestYearViewViewController.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2019-05-11. 6 | // 7 | 8 | import UIKit 9 | import JTAppleCalendar 10 | 11 | class TestYearViewViewController: UIViewController { 12 | @IBOutlet var calendarView: JTACYearView! 13 | let f = DateFormatter() 14 | 15 | override func viewDidLoad() { 16 | calendarView.calendarDataSource = self 17 | calendarView.calendarDelegate = self 18 | super.viewDidLoad() 19 | } 20 | } 21 | 22 | 23 | extension TestYearViewViewController: JTACYearViewDelegate, JTACYearViewDataSource { 24 | // Drawing for a whole month cell 25 | func calendar(_ calendar: JTACYearView, cellFor item: Any, at date: Date, indexPath: IndexPath) -> JTACMonthCell { 26 | if item is Month { 27 | let cell = calendar.dequeueReusableJTAppleMonthCell(withReuseIdentifier: "kkk", for: indexPath) as! MyCell 28 | f.dateFormat = "MMM" 29 | cell.monthLabel.text = f.string(from: date) 30 | return cell 31 | } else { 32 | let cell = calendar.dequeueReusableJTAppleMonthCell(withReuseIdentifier: "zzz", for: indexPath) as! YearHeaderCell 33 | f.dateFormat = "yyyy" 34 | cell.yearLabel.text = f.string(from: date) 35 | return cell 36 | } 37 | } 38 | 39 | 40 | 41 | func configureCalendar(_ calendar: JTACYearView) -> (configurationParameters: ConfigurationParameters, months: [Any]) { 42 | let df = DateFormatter() 43 | df.dateFormat = "yyyy MM dd" 44 | 45 | let sDate = df.date(from: "2019 01 01")! 46 | let eDate = df.date(from: "2050 05 31")! 47 | 48 | let configParams = ConfigurationParameters(startDate: sDate, 49 | endDate: eDate, 50 | numberOfRows: 6, 51 | calendar: Calendar(identifier: .gregorian), 52 | generateInDates: .forAllMonths, 53 | generateOutDates: .tillEndOfGrid, 54 | firstDayOfWeek: .sunday, 55 | hasStrictBoundaries: true) 56 | 57 | // Get year data 58 | let dataSource = calendar.dataSourcefrom(configurationParameters: configParams) 59 | 60 | // Modify the data source to include a String every 12 data elements. 61 | // This string type will be used to add a header. 62 | var modifiedDataSource: [Any] = [] 63 | for index in (0.. CGSize { 88 | if item is Month { 89 | let width = (calendar.frame.width - 41 ) / 3 90 | let height = width 91 | return CGSize(width: width, height: height) 92 | } else { 93 | let width = calendar.frame.width - 41 94 | let height:CGFloat = 20 95 | return CGSize(width: width, height: height) 96 | } 97 | } 98 | } 99 | 100 | 101 | class MyCell: JTACMonthCell { 102 | @IBOutlet var monthLabel: UILabel! 103 | } 104 | 105 | class YearHeaderCell: JTACMonthCell { 106 | @IBOutlet var yearLabel: UILabel! 107 | } 108 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleDateCells/DateCellCreatedWithCode/CodeCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeCellView.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2016-05-26. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JTAppleCalendar 11 | 12 | class CodeCellView: JTACDayCell { 13 | let bgColor = UIColor.red 14 | // Only override drawRect: if you perform custom drawing. 15 | // An empty implementation adversely affects performance during animation. 16 | 17 | override func draw(_ rect: CGRect) { 18 | let context = UIGraphicsGetCurrentContext() 19 | context?.setFillColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0) 20 | let r1 = CGRect(x: 0, y: 0, width: 25, height: 25) 21 | context?.addRect(r1) 22 | context?.fillPath() 23 | context?.setStrokeColor(red: 1.0, green: 1.0, blue: 0.5, alpha: 1.0) 24 | context?.addEllipse(in: CGRect(x: 0, y: 0, width: 25, height: 25)) 25 | context?.strokePath() 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleDateCells/DateCellCreatedWithXIB/CellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellView.swift 3 | // testApplicationCalendar 4 | // 5 | // Created by JayT on 2016-03-04. 6 | // Copyright © 2016 OS-Tech. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JTAppleCalendar 11 | 12 | class CellView: JTACDayCell { 13 | @IBOutlet var selectedView: UIView! 14 | @IBOutlet var dayLabel: UILabel! 15 | @IBOutlet var monthLabel: UILabel! 16 | } 17 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleDateCells/DateCellCreatedWithXIB/CellView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsClass/CodePinkSectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodePinkSectionHeaderView.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2016-07-15. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JTAppleCalendar 11 | 12 | class CodePinkSectionHeaderView: JTACMonthReusableView { 13 | 14 | override func draw(_ rect: CGRect) { 15 | let context = UIGraphicsGetCurrentContext()! 16 | context.setFillColor(red: 1.0, green: 0.5, blue: 0.0, alpha: 1.0) 17 | let r1 = CGRect(x: 0, y: 0, width: 25, height: 25) 18 | context.addRect(r1) 19 | context.fillPath() 20 | context.setStrokeColor(red: 1.0, green: 1.0, blue: 0.5, alpha: 1.0) 21 | context.addEllipse(in: CGRect(x: 0, y: 0, width: 25, height: 25)) 22 | context.strokePath() 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsClass/CodeWhiteSectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CodeWhiteSectionHeaderView.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2016-07-15. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JTAppleCalendar 11 | 12 | class CodeWhiteSectionHeaderView: JTACMonthReusableView { 13 | // Only override drawRect: if you perform custom drawing. 14 | // An empty implementation adversely affects performance during animation. 15 | 16 | override func draw(_ rect: CGRect) { 17 | let context = UIGraphicsGetCurrentContext()! 18 | context.setFillColor(red: 1.0, green: 2.5, blue: 0.3, alpha: 1.0) 19 | let r1 = CGRect(x: 0, y: 0, width: 25, height: 25) // Size 20 | context.addRect(r1) 21 | context.fillPath() 22 | context.setStrokeColor(red: 1.0, green: 1.0, blue: 0.5, alpha: 1.0) 23 | context.addEllipse(in: CGRect(x: 0, y: 0, width: 25, height: 25)) 24 | context.strokePath() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsXibs/PinkSectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinkSectionHeaderView.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2016-05-11. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import JTAppleCalendar 11 | 12 | class PinkSectionHeaderView: JTACMonthReusableView { 13 | @IBOutlet var title: UILabel! 14 | } 15 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsXibs/PinkSectionHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsXibs/WhiteSectionHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhiteSectionHeaderView.swift 3 | // JTAppleCalendar 4 | // 5 | // Created by JayT on 2016-05-16. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | import UIKit 9 | import JTAppleCalendar 10 | 11 | class WhiteSectionHeaderView: JTACMonthReusableView { 12 | @IBOutlet weak var title: UILabel! 13 | } 14 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/ExampleSectionHeaders/HeaderAsXibs/WhiteSectionHeaderView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | UISceneStoryboardFile 39 | Main 40 | 41 | 42 | 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /SampleJTAppleCalendar/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SampleJTAppleCalendar 4 | // 5 | // Created by Jeron Thomas on 2019-06-25. 6 | // Copyright © 2019 OsTech. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /SampleJTAppleCalendarUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SampleJTAppleCalendarUITests/SampleJTAppleCalendarUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleJTAppleCalendarUITests.swift 3 | // SampleJTAppleCalendarUITests 4 | // 5 | // Created by Jeron Thomas on 2019-11-16. 6 | // Copyright © 2019 OsTech. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SampleJTAppleCalendarUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | print("") 31 | 32 | 33 | 34 | // Use recording to get started writing UI tests. 35 | // Use XCTAssert and related functions to verify your tests produce the correct results. 36 | } 37 | 38 | func testLaunchPerformance() { 39 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 40 | // This measures how long it takes to launch your application. 41 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 42 | XCUIApplication().launch() 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/CalendarEnums.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarEnums.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | 27 | /// Describes a scroll destination 28 | public enum SegmentDestination { 29 | /// next the destination is the following segment 30 | case next 31 | /// previous the destination is the previous segment 32 | case previous 33 | /// start the destination is the start segment 34 | case start 35 | /// end the destination is the end segment 36 | case end 37 | } 38 | /// Describes the types of out-date cells to be generated. 39 | public enum OutDateCellGeneration { 40 | /// tillEndOfRow will generate dates till it reaches the end of a row. 41 | /// endOfGrid will continue generating until it has filled a 6x7 grid. 42 | /// Off-mode will generate no postdates 43 | case tillEndOfRow, tillEndOfGrid, off 44 | } 45 | 46 | /// Describes the types of out-date cells to be generated. 47 | public enum InDateCellGeneration { 48 | /// forFirstMonthOnly will generate dates for the first month only 49 | /// forAllMonths will generate dates for all months 50 | /// off setting will generate no dates 51 | case forFirstMonthOnly, forAllMonths, off 52 | } 53 | 54 | /// Describes the calendar reading direction 55 | /// Useful for regions that read text from right to left 56 | public enum ReadingOrientation { 57 | /// Reading orientation is from right to left 58 | case rightToLeft 59 | /// Reading orientation is from left to right 60 | case leftToRight 61 | } 62 | 63 | /// Configures the behavior of the scrolling mode of the calendar 64 | public enum ScrollingMode: Equatable { 65 | /// stopAtEachCalendarFrame - non-continuous scrolling that will stop at each frame 66 | case stopAtEachCalendarFrame 67 | /// stopAtEachSection - non-continuous scrolling that will stop at each section 68 | case stopAtEachSection 69 | /// stopAtEach - non-continuous scrolling that will stop at each custom interval 70 | case stopAtEach(customInterval: CGFloat) 71 | /// nonStopToSection - continuous scrolling that will stop at a section 72 | case nonStopToSection(withResistance: CGFloat) 73 | /// nonStopToCell - continuous scrolling that will stop at a cell 74 | case nonStopToCell(withResistance: CGFloat) 75 | /// nonStopTo - continuous scrolling that will stop at acustom interval, do not use 0 as custom interval 76 | case nonStopTo(customInterval: CGFloat, withResistance: CGFloat) 77 | /// none - continuous scrolling that will eventually stop at a point 78 | case none 79 | 80 | func pagingIsEnabled() -> Bool { 81 | switch self { 82 | case .stopAtEachCalendarFrame: return true 83 | default: return false 84 | } 85 | } 86 | 87 | public static func ==(lhs: ScrollingMode, rhs: ScrollingMode) -> Bool { 88 | switch (lhs, rhs) { 89 | case (.none, .none), 90 | (.stopAtEachCalendarFrame, .stopAtEachCalendarFrame), 91 | (.stopAtEachSection, .stopAtEachSection): return true 92 | case (let .stopAtEach(customInterval: v1), let .stopAtEach(customInterval: v2)): return v1 == v2 93 | case (let .nonStopToSection(withResistance: v1), let .nonStopToSection(withResistance: v2)): return v1 == v2 94 | case (let .nonStopToCell(withResistance: v1), let .nonStopToCell(withResistance: v2)): return v1 == v2 95 | case (let .nonStopTo(customInterval: v1, withResistance: x1), let .nonStopTo(customInterval: v2, withResistance: x2)): return v1 == v2 && x1 == x2 96 | default: return false 97 | } 98 | } 99 | } 100 | 101 | /// Describes which month owns the date 102 | public enum DateOwner: Int { 103 | /// Describes which month owns the date 104 | case thisMonth = 0, 105 | previousMonthWithinBoundary, 106 | previousMonthOutsideBoundary, 107 | followingMonthWithinBoundary, 108 | followingMonthOutsideBoundary 109 | } 110 | /// Months of the year 111 | public enum MonthsOfYear: Int, CaseIterable { 112 | case jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec 113 | } 114 | 115 | /// Selection position of a range-selected date cell 116 | public enum SelectionRangePosition: Int { 117 | /// Selection position 118 | case left = 1, middle, right, full, none 119 | } 120 | 121 | /// Between month segments, the range selection can either be visually disconnected or connected 122 | public enum RangeSelectionMode { 123 | case segmented, continuous 124 | } 125 | 126 | /// Signifies whether or not a selection was done programatically or by the user 127 | public enum SelectionType: String { 128 | /// Selection type 129 | case programatic, userInitiated 130 | } 131 | 132 | /// Days of the week. By setting your calendar's first day of the week, 133 | /// you can change which day is the first for the week. Sunday is the default value. 134 | public enum DaysOfWeek: Int, CaseIterable { 135 | /// Days of the week. 136 | case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday 137 | } 138 | 139 | internal enum DelayedTaskType { 140 | case scroll, general 141 | } 142 | 143 | internal enum SelectionAction { 144 | case didSelect, didDeselect 145 | } 146 | 147 | internal enum ShouldSelectionAction { 148 | case shouldSelect, shouldDeselect 149 | } 150 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/GlobalFunctionsAndExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlobalFunctionsAndExtensions.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Calendar { 28 | static let formatter: DateFormatter = { 29 | let dateFormatter = DateFormatter() 30 | dateFormatter.dateFormat = "yyyy MM dd" 31 | dateFormatter.isLenient = true 32 | return dateFormatter 33 | }() 34 | 35 | 36 | func startOfMonth(for date: Date) -> Date? { 37 | guard let interval = self.dateInterval(of: .month, for: date) else { return nil } 38 | return interval.start 39 | } 40 | 41 | func endOfMonth(for date: Date) -> Date? { 42 | guard let interval = self.dateInterval(of: .month, for: date) else { return nil } 43 | return self.date(byAdding: DateComponents(day: -1), to: interval.end) 44 | } 45 | 46 | private func dateFormatterComponents(from date: Date) -> (month: Int, year: Int)? { 47 | 48 | // Setup the dateformatter to this instance's settings 49 | Calendar.formatter.timeZone = self.timeZone 50 | Calendar.formatter.locale = self.locale 51 | Calendar.formatter.calendar = self 52 | 53 | let comp = self.dateComponents([.year, .month], from: date) 54 | 55 | guard 56 | let month = comp.month, 57 | let year = comp.year else { 58 | return nil 59 | } 60 | return (month, year) 61 | } 62 | } 63 | 64 | extension Dictionary where Value: Equatable { 65 | func key(for value: Value) -> Key? { 66 | guard let index = firstIndex(where: { $0.1 == value }) else { 67 | return nil 68 | } 69 | return self[index].0 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACCollectionYearViewDelegates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACCollectionYearViewDelegates.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | import Foundation 27 | 28 | extension JTACYearView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 29 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 30 | return monthData.count 31 | } 32 | 33 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 34 | guard 35 | let delegate = calendarDelegate, 36 | monthData.count > indexPath.item else { 37 | print("Invalid startup parameters. Exiting calendar setup.") 38 | assert(false) 39 | return UICollectionViewCell() 40 | } 41 | 42 | if let monthData = monthData[indexPath.item] as? Month { 43 | guard let date = configurationParameters.calendar.date(byAdding: .month, value: monthData.index, to: configurationParameters.startDate) else { 44 | print("Invalid startup parameters. Exiting calendar setup.") 45 | assert(false) 46 | return UICollectionViewCell() 47 | } 48 | 49 | let cell = delegate.calendar(self, cellFor: self.monthData[indexPath.item], at: date, indexPath: indexPath) 50 | cell.setupWith(configurationParameters: configurationParameters, 51 | month: monthData, 52 | delegate: self) 53 | return cell 54 | } else { 55 | let date = findFirstMonthCellDate(cellIndex: indexPath.item, monthData: monthData) 56 | return delegate.calendar(self, cellFor: self.monthData[indexPath.item], at: date, indexPath: indexPath) 57 | } 58 | } 59 | 60 | func findFirstMonthCellDate(cellIndex: Int, monthData: [Any]) -> Date { 61 | var retval = configurationParameters.endDate 62 | for index in cellIndex.. CGSize { 78 | guard let size = calendarDelegate?.calendar(self, sizeFor: monthData[indexPath.item]) else { 79 | let width: CGFloat = monthData[indexPath.item] is Month ? (frame.width - 40) / 3 : frame.width 80 | let height = width 81 | return CGSize(width: width, height: height) 82 | } 83 | return size 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACDayCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACDayCell.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | 27 | /// The JTAppleDayCell class defines the attributes and 28 | /// behavior of the cells that appear in JTAppleCalendarMonthView objects. 29 | @available(*, unavailable, renamed: "JTACDayCell") 30 | open class JTAppleCell: UICollectionViewCell{} 31 | open class JTACDayCell: UICollectionViewCell { 32 | @available(*, message: "Using isSelected only to determing when selection occurs is ok. For other cases please use cellState.isSelected to avoid synchronization issues.") 33 | open override var isSelected: Bool { 34 | get { return super.isSelected } 35 | set { super.isSelected = newValue} 36 | } 37 | 38 | /// Cell view that will be customized 39 | public override init(frame: CGRect) { 40 | super.init(frame: frame) 41 | } 42 | 43 | /// Returns an object initialized from data in a given unarchiver. 44 | required public init?(coder aDecoder: NSCoder) { 45 | super.init(coder: aDecoder) 46 | } 47 | 48 | /// Prepares the receiver for service after it has been loaded from an Interface Builder archive, or nib file. 49 | open override func awakeFromNib() { 50 | super.awakeFromNib() 51 | 52 | self.contentView.frame = self.bounds 53 | self.contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACInteractionYearFunctions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserInteractionYearFunctions.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension JTACYearView { 28 | 29 | /// Dequeues a reuable calendar cell 30 | public func dequeueReusableJTAppleMonthCell(withReuseIdentifier identifier: String, for indexPath: IndexPath) -> JTACMonthCell { 31 | guard let cell = dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? JTACMonthCell else { 32 | assert(false, "Error initializing Cell View with identifier: '\(identifier)'") 33 | return JTACMonthCell() 34 | } 35 | return cell 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthCell.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | public protocol JTACCellMonthViewDelegate: AnyObject { 29 | func monthView(_ monthView: JTACCellMonthView, 30 | drawingFor segmentRect: CGRect, 31 | with date: Date, 32 | dateOwner: DateOwner, 33 | monthIndex: Int) 34 | } 35 | 36 | open class JTACMonthCell: UICollectionViewCell { 37 | @IBOutlet var monthView: JTACCellMonthView? 38 | weak var delegate: JTACCellMonthViewDelegate? 39 | 40 | func setupWith(configurationParameters: ConfigurationParameters, 41 | month: Month, 42 | delegate: JTACCellMonthViewDelegate) { 43 | guard let monthView = monthView else { assert(false); return } 44 | self.delegate = delegate 45 | monthView.setupWith(configurationParameters: configurationParameters, 46 | month: month, 47 | delegate: self) 48 | } 49 | } 50 | 51 | extension JTACMonthCell: JTACCellMonthViewDelegate { 52 | public func monthView(_ monthView: JTACCellMonthView, 53 | drawingFor segmentRect: CGRect, 54 | with date: Date, 55 | dateOwner: DateOwner, 56 | monthIndex: Int) { 57 | delegate?.monthView(monthView, drawingFor: segmentRect, with: date, dateOwner: dateOwner, monthIndex: monthIndex) 58 | } 59 | } 60 | 61 | 62 | 63 | 64 | open class JTACCellMonthView: UIView { 65 | var sectionInset = UIEdgeInsets.zero 66 | var month: Month! 67 | var configurationParameters: ConfigurationParameters! 68 | weak var delegate: JTACCellMonthViewDelegate? 69 | var scrollDirection: UICollectionView.ScrollDirection = .horizontal 70 | 71 | func setupWith(configurationParameters: ConfigurationParameters, month: Month, delegate: JTACCellMonthViewDelegate? = nil) { 72 | self.configurationParameters = configurationParameters 73 | self.delegate = delegate 74 | self.month = month 75 | 76 | setNeedsDisplay() // force reloading of the drawRect code to update the view. 77 | } 78 | 79 | override open func draw(_ rect: CGRect) { 80 | super.draw(rect) 81 | 82 | var xCellOffset: CGFloat = 0 83 | var yCellOffset: CGFloat = 0 84 | 85 | let numberOfDaysInCurrentSection = month.sections.first! 86 | for dayCounter in 1...numberOfDaysInCurrentSection { 87 | 88 | let width = (frame.width - ((sectionInset.left / 7) + (sectionInset.right / 7))) / 7 89 | let height = (frame.height - sectionInset.top - sectionInset.bottom) / 6 90 | 91 | let rect = CGRect(x: xCellOffset, y: yCellOffset, width: width, height: height) 92 | guard let dateWithOwner = dateFromIndex(dayCounter - 1, month: month, 93 | startOfMonthCache: configurationParameters.startDate, 94 | endOfMonthCache: configurationParameters.endDate) else { continue } 95 | 96 | 97 | delegate?.monthView(self, 98 | drawingFor: rect, 99 | with: dateWithOwner.date, 100 | dateOwner: dateWithOwner.owner, 101 | monthIndex: month.index) 102 | 103 | xCellOffset += width 104 | 105 | if dayCounter == numberOfDaysInCurrentSection || dayCounter % maxNumberOfDaysInWeek == 0 { 106 | // We are at the last item in the section 107 | // && if we have headers 108 | xCellOffset = sectionInset.left 109 | yCellOffset += height 110 | } 111 | } 112 | } 113 | 114 | private func dateFromIndex(_ index: Int, month: Month, startOfMonthCache: Date, endOfMonthCache: Date) -> (date: Date, owner: DateOwner)? { // Returns nil if date is out of scope 115 | // Calculate the offset 116 | let offSet = month.inDates 117 | 118 | let dayIndex = month.startDayIndex + index - offSet 119 | var dateOwner: DateOwner 120 | 121 | guard let validDate = configurationParameters.calendar.date(byAdding: .day, value: dayIndex, to: startOfMonthCache) else { return nil } 122 | 123 | if index >= offSet && index < month.numberOfDaysInMonth + offSet { 124 | dateOwner = .thisMonth 125 | } else if index < offSet { 126 | // This is a preDate 127 | 128 | if validDate < startOfMonthCache { 129 | dateOwner = .previousMonthOutsideBoundary 130 | } else { 131 | dateOwner = .previousMonthWithinBoundary 132 | } 133 | } else { 134 | // This is a postDate 135 | if validDate > endOfMonthCache { 136 | dateOwner = .followingMonthOutsideBoundary 137 | } else { 138 | dateOwner = .followingMonthWithinBoundary 139 | } 140 | } 141 | return (validDate, dateOwner) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthDelegateProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthDelegateProtocol.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | import Foundation 25 | import UIKit 26 | 27 | protocol JTACMonthDelegateProtocol: AnyObject { 28 | // Variables 29 | var allowsDateCellStretching: Bool {get set} 30 | var _cachedConfiguration: ConfigurationParameters? {get set} 31 | var calendarDataSource: JTACMonthViewDataSource? {get set} 32 | var cellSize: CGFloat {get set} 33 | var anchorDate: Date? {get set} 34 | var calendarLayoutIsLoaded: Bool {get} 35 | var minimumInteritemSpacing: CGFloat {get set} 36 | var minimumLineSpacing: CGFloat {get set} 37 | var monthInfo: [Month] {get set} 38 | var monthMap: [Int: Int] {get set} 39 | var scrollDirection: UICollectionView.ScrollDirection {get set} 40 | var sectionInset: UIEdgeInsets {get set} 41 | var totalDays: Int {get} 42 | var requestedContentOffset: CGPoint {get} 43 | 44 | // Functions 45 | func pathsFromDates(_ dates: [Date]) -> [IndexPath] 46 | func sizeOfDecorationView(indexPath: IndexPath) -> CGRect 47 | func sizesForMonthSection() -> [AnyHashable:CGFloat] 48 | func targetPointForItemAt(indexPath: IndexPath, preferredScrollPosition: UICollectionView.ScrollPosition?) -> CGPoint? 49 | } 50 | 51 | extension JTACMonthView: JTACMonthDelegateProtocol { } 52 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthLayoutHorizontalCalendar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthLayoutHorizontalCalendar.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | extension JTACMonthLayout { 26 | func configureHorizontalLayout() { 27 | var virtualSection = 0 28 | var totalDayCounter = 0 29 | let fullSection = numberOfRows * maxNumberOfDaysInWeek 30 | 31 | xCellOffset = sectionInset.left 32 | endSeparator = sectionInset.left + sectionInset.right 33 | 34 | for aMonth in monthInfo { 35 | for numberOfDaysInCurrentSection in aMonth.sections { 36 | // Generate and cache the headers 37 | if let aHeaderAttr = determineToApplySupplementaryAttribs(0, section: virtualSection) { 38 | headerCache[virtualSection] = aHeaderAttr 39 | if strictBoundaryRulesShouldApply { 40 | contentWidth += aHeaderAttr.width 41 | yCellOffset = aHeaderAttr.height 42 | } 43 | } 44 | // Generate and cache the cells 45 | for dayCounter in 1...numberOfDaysInCurrentSection { 46 | guard let attribute = determineToApplyAttribs(dayCounter - 1, section: virtualSection) else { continue } 47 | if cellCache[virtualSection] == nil { cellCache[virtualSection] = [] } 48 | cellCache[virtualSection]!.append(attribute) 49 | lastWrittenCellAttribute = attribute 50 | xCellOffset += attribute.width 51 | 52 | if strictBoundaryRulesShouldApply { 53 | if dayCounter == numberOfDaysInCurrentSection || dayCounter % maxNumberOfDaysInWeek == 0 { 54 | // We are at the last item in the section 55 | // && if we have headers 56 | xCellOffset = sectionInset.left 57 | yCellOffset += attribute.height 58 | } 59 | } else { 60 | totalDayCounter += 1 61 | if totalDayCounter % fullSection == 0 { 62 | yCellOffset = 0 63 | xCellOffset = sectionInset.left 64 | contentWidth += (attribute.width * 7) + endSeparator 65 | xStride = contentWidth 66 | endOfSectionOffsets.append(contentWidth) 67 | } else { 68 | if totalDayCounter >= delegate?.totalDays ?? 0 { 69 | contentWidth += (attribute.width * 7) + endSeparator 70 | endOfSectionOffsets.append(contentWidth) 71 | } 72 | if totalDayCounter % maxNumberOfDaysInWeek == 0 { 73 | xCellOffset = sectionInset.left 74 | yCellOffset += attribute.height 75 | } 76 | } 77 | } 78 | } 79 | 80 | // Save the content size for each section 81 | if strictBoundaryRulesShouldApply { 82 | contentWidth += endSeparator 83 | endOfSectionOffsets.append(contentWidth) 84 | xStride = endOfSectionOffsets[virtualSection] 85 | } 86 | virtualSection += 1 87 | } 88 | } 89 | contentHeight = self.collectionView!.bounds.size.height 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthLayoutProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthLayoutProtocol.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import UIKit 26 | 27 | protocol JTACMonthLayoutProtocol: AnyObject { 28 | var minimumInteritemSpacing: CGFloat {get set} 29 | var minimumLineSpacing: CGFloat {get set} 30 | var sectionInset: UIEdgeInsets {get set} 31 | var scrollDirection: UICollectionView.ScrollDirection {get set} 32 | } 33 | 34 | extension UICollectionViewFlowLayout: JTACMonthLayoutProtocol {} 35 | 36 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthLayoutVerticalCalendar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthLayoutVerticalCalendar.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | extension JTACMonthLayout { 26 | 27 | func configureVerticalLayout() { 28 | var virtualSection = 0 29 | var totalDayCounter = 0 30 | let fullSection = numberOfRows * maxNumberOfDaysInWeek 31 | 32 | xCellOffset = sectionInset.left 33 | yCellOffset = sectionInset.top 34 | contentHeight = sectionInset.top 35 | endSeparator = sectionInset.top + sectionInset.bottom 36 | 37 | for aMonth in monthInfo { 38 | for numberOfDaysInCurrentSection in aMonth.sections { 39 | // Generate and cache the headers 40 | if let aHeaderAttr = determineToApplySupplementaryAttribs(0, section: virtualSection) { 41 | headerCache[virtualSection] = aHeaderAttr 42 | if strictBoundaryRulesShouldApply { 43 | contentHeight += aHeaderAttr.height 44 | yCellOffset += aHeaderAttr.height 45 | } 46 | } 47 | // Generate and cache the cells 48 | for dayCounter in 1...numberOfDaysInCurrentSection { 49 | totalDayCounter += 1 50 | guard let attribute = determineToApplyAttribs(dayCounter - 1, section: virtualSection) else { continue } 51 | if cellCache[virtualSection] == nil { cellCache[virtualSection] = [] } 52 | cellCache[virtualSection]!.append(attribute) 53 | lastWrittenCellAttribute = attribute 54 | xCellOffset += attribute.width 55 | 56 | if strictBoundaryRulesShouldApply { 57 | if dayCounter == numberOfDaysInCurrentSection || dayCounter % maxNumberOfDaysInWeek == 0 { 58 | // We are at the last item in the 59 | // section && if we have headers 60 | 61 | xCellOffset = sectionInset.left 62 | yCellOffset += attribute.height 63 | contentHeight += attribute.height 64 | 65 | if dayCounter == numberOfDaysInCurrentSection { 66 | yCellOffset += sectionInset.top 67 | contentHeight += sectionInset.top 68 | endOfSectionOffsets.append(contentHeight - sectionInset.top) 69 | } 70 | } 71 | } else { 72 | if totalDayCounter % fullSection == 0 { 73 | 74 | yCellOffset += attribute.height + sectionInset.top 75 | xCellOffset = sectionInset.left 76 | contentHeight = yCellOffset 77 | endOfSectionOffsets.append(contentHeight - sectionInset.top) 78 | 79 | } else { 80 | if totalDayCounter >= delegate?.totalDays ?? 0 { 81 | yCellOffset += attribute.height + sectionInset.top 82 | contentHeight = yCellOffset 83 | endOfSectionOffsets.append(contentHeight - sectionInset.top) 84 | } 85 | 86 | if totalDayCounter % maxNumberOfDaysInWeek == 0 { 87 | xCellOffset = sectionInset.left 88 | yCellOffset += attribute.height 89 | } 90 | } 91 | } 92 | } 93 | virtualSection += 1 94 | } 95 | } 96 | contentWidth = self.collectionView!.bounds.size.width 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthReusableView.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | /// The header view class of the calendar 29 | @available(*, unavailable, renamed: "JTACMonthReusableView") 30 | open class JTAppleCollectionReusableView: UICollectionReusableView {} 31 | open class JTACMonthReusableView: UICollectionReusableView { 32 | /// Initializes and returns a newly allocated view object with the specified frame rectangle. 33 | public override init(frame: CGRect) { 34 | super.init(frame: frame) 35 | } 36 | 37 | /// Returns an object initialized from data in a given unarchiver. 38 | /// self, initialized using the data in decoder. 39 | required public init?(coder aDecoder: NSCoder) { 40 | super.init(coder: aDecoder) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthView.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | let maxNumberOfDaysInWeek = 7 // Should not be changed 29 | let maxNumberOfRowsPerMonth = 6 // Should not be changed 30 | let developerErrorMessage = "There was an error in this code section. Please contact the developer on GitHub" 31 | let decorationViewID = "Are you ready for the life after this one?" 32 | let errorDelta: CGFloat = 0.0000001 33 | 34 | 35 | /// An instance of JTAppleCalendarMonthView (or simply, a calendar view) is a 36 | /// means for displaying and interacting with a gridstyle layout of date-cells 37 | @available(*, unavailable, renamed: "JTACMonthView") 38 | open class JTAppleCalendarView: UICollectionView {} 39 | open class JTACMonthView: UICollectionView { 40 | 41 | /// Configures the size of your date cells 42 | @IBInspectable open var cellSize: CGFloat = 0 { 43 | didSet { 44 | if oldValue == cellSize { return } 45 | calendarViewLayout.invalidateLayout() 46 | } 47 | } 48 | 49 | /// Stores the first and last selected date cel 50 | open var selectedCells: (first: (date: Date, indexPath: IndexPath)?, last: (date: Date, indexPath: IndexPath)?) 51 | 52 | /// The scroll direction of the sections in JTAppleCalendar. 53 | open var scrollDirection: UICollectionView.ScrollDirection = .horizontal 54 | 55 | /// The configuration parameters setup by the developer in the confogureCalendar function 56 | open var cachedConfiguration: ConfigurationParameters? { return _cachedConfiguration } 57 | 58 | /// Enables/Disables the stretching of date cells. When enabled cells will stretch to fit the width of a month in case of a <= 5 row month. 59 | open var allowsDateCellStretching = true 60 | 61 | /// Alerts the calendar that range selection will be checked. If you are 62 | /// not using rangeSelection and you enable this, 63 | /// then whenever you click on a datecell, you may notice a very fast 64 | /// refreshing of the date-cells both left and right of the cell you 65 | /// just selected. 66 | open var allowsRangedSelection: Bool = false 67 | 68 | open var rangeSelectionMode: RangeSelectionMode = .segmented 69 | 70 | /// The object that acts as the delegate of the calendar view. 71 | weak open var calendarDelegate: JTACMonthViewDelegate? { 72 | didSet { lastMonthSize = sizesForMonthSection() } 73 | } 74 | 75 | /// The object that acts as the data source of the calendar view. 76 | weak open var calendarDataSource: JTACMonthViewDataSource? { 77 | didSet { setupMonthInfoAndMap() } // Refetch the data source for a data source change 78 | } 79 | 80 | var triggerScrollToDateDelegate: Bool? = true 81 | var isScrollInProgress = false 82 | var isReloadDataInProgress = false 83 | 84 | // Keeps track of scroll target location. If isScrolling, and user taps while scrolling 85 | var endScrollTargetLocation: CGFloat = 0 86 | var lastMovedScrollDirection: CGFloat = 0 87 | 88 | var generalDelayedExecutionClosure: [(() -> Void)] = [] 89 | var scrollDelayedExecutionClosure: [(() -> Void)] = [] 90 | 91 | let dateGenerator = JTAppleDateConfigGenerator.shared 92 | 93 | /// Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated. 94 | public init() { 95 | super.init(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) 96 | setupNewLayout(from: collectionViewLayout as! JTACMonthLayoutProtocol) 97 | } 98 | 99 | /// Initializes and returns a newly allocated collection view object with the specified frame and layout. 100 | @available(*, unavailable, message: "Please use JTAppleCalendarMonthView() instead. It manages its own layout.") 101 | public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { 102 | super.init(frame: frame, collectionViewLayout: UICollectionViewFlowLayout()) 103 | setupNewLayout(from: collectionViewLayout as! JTACMonthLayoutProtocol) 104 | } 105 | 106 | /// Initializes using decoder object 107 | required public init?(coder aDecoder: NSCoder) { 108 | super.init(coder: aDecoder) 109 | setupNewLayout(from: collectionViewLayout as! JTACMonthLayoutProtocol) 110 | } 111 | 112 | // Configuration parameters from the dataSource 113 | var _cachedConfiguration: ConfigurationParameters? 114 | // Set the start of the month 115 | var startOfMonthCache: Date! 116 | // Set the end of month 117 | var endOfMonthCache: Date! 118 | var selectedCellData: [IndexPath:SelectedCellData] = [:] 119 | var pathsToReload: Set = [] //Paths to reload because of prefetched cells 120 | 121 | var anchorDate: Date? 122 | 123 | var requestedContentOffset: CGPoint { 124 | var retval = CGPoint(x: -contentInset.left, y: -contentInset.top) 125 | guard let date = anchorDate else { return retval } 126 | 127 | // reset the initial scroll date once used. 128 | anchorDate = nil 129 | 130 | // Ensure date is within valid boundary 131 | let components = calendar.dateComponents([.year, .month, .day], from: date) 132 | let firstDayOfDate = calendar.date(from: components)! 133 | if !((firstDayOfDate >= startOfMonthCache!) && (firstDayOfDate <= endOfMonthCache!)) { return retval } 134 | 135 | // Get valid indexPath of date to scroll to 136 | let retrievedPathsFromDates = pathsFromDates([date]) 137 | if retrievedPathsFromDates.isEmpty { return retval } 138 | let sectionIndexPath = pathsFromDates([date])[0] 139 | 140 | 141 | if calendarViewLayout.thereAreHeaders && scrollDirection == .vertical { 142 | let indexPath = IndexPath(item: 0, section: sectionIndexPath.section) 143 | guard let attributes = calendarViewLayout.layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: indexPath) else { return retval } 144 | 145 | let maxYCalendarOffset = max(0, self.contentSize.height - self.frame.size.height) 146 | retval = CGPoint(x: attributes.frame.origin.x,y: min(maxYCalendarOffset, attributes.frame.origin.y)) 147 | // if self.scrollDirection == .horizontal { topOfHeader.x += extraAddedOffset} else { topOfHeader.y += extraAddedOffset } 148 | 149 | } else { 150 | switch scrollingMode { 151 | case .stopAtEach, .stopAtEachSection, .stopAtEachCalendarFrame: 152 | if scrollDirection == .horizontal || (scrollDirection == .vertical && !calendarViewLayout.thereAreHeaders) { 153 | retval = self.targetPointForItemAt(indexPath: sectionIndexPath) ?? retval 154 | } 155 | default: 156 | break 157 | } 158 | } 159 | return retval 160 | } 161 | 162 | var _sectionInset: UIEdgeInsets = .zero 163 | open var sectionInset: UIEdgeInsets { 164 | set { 165 | _sectionInset.top = newValue.top < 0 ? 0 : newValue.top 166 | _sectionInset.bottom = newValue.bottom < 0 ? 0 : newValue.bottom 167 | _sectionInset.left = newValue.left < 0 ? 0 : newValue.left 168 | _sectionInset.right = newValue.right < 0 ? 0 : newValue.right 169 | } 170 | get { return _sectionInset } 171 | } 172 | 173 | var _minimumInteritemSpacing: CGFloat = 0 174 | open var minimumInteritemSpacing: CGFloat { 175 | set { _minimumInteritemSpacing = newValue < 0 ? 0 : newValue } 176 | get { return _minimumInteritemSpacing } 177 | } 178 | 179 | var _minimumLineSpacing: CGFloat = 0 180 | open var minimumLineSpacing: CGFloat { 181 | set { _minimumLineSpacing = newValue < 0 ? 0 : newValue } 182 | get { return _minimumLineSpacing } 183 | } 184 | 185 | lazy var theData: CalendarData = { 186 | return self.setupMonthInfoDataForStartAndEndDate() 187 | }() 188 | 189 | var lastMonthSize: [AnyHashable:CGFloat] = [:] 190 | 191 | var monthMap: [Int: Int] { 192 | get { return theData.sectionToMonthMap } 193 | set { theData.sectionToMonthMap = newValue } 194 | } 195 | 196 | var decelerationRateMatchingScrollingMode: CGFloat { 197 | switch scrollingMode { 198 | case .stopAtEachCalendarFrame: return UIScrollView.DecelerationRate.fast.rawValue 199 | case .stopAtEach, .stopAtEachSection: return UIScrollView.DecelerationRate.fast.rawValue 200 | case .nonStopToSection, .nonStopToCell, .nonStopTo, .none: return UIScrollView.DecelerationRate.normal.rawValue 201 | } 202 | } 203 | 204 | /// Configure the scrolling behavior 205 | open var scrollingMode: ScrollingMode = .stopAtEachCalendarFrame { 206 | didSet { 207 | decelerationRate = UIScrollView.DecelerationRate(rawValue: decelerationRateMatchingScrollingMode) 208 | #if os(iOS) 209 | switch scrollingMode { 210 | case .stopAtEachCalendarFrame: 211 | isPagingEnabled = true 212 | default: 213 | isPagingEnabled = false 214 | } 215 | #endif 216 | } 217 | } 218 | } 219 | 220 | extension JTACMonthView { 221 | /// A semantic description of the view’s contents, used to determine whether the view should be flipped when switching between left-to-right and right-to-left layouts. 222 | open override var semanticContentAttribute: UISemanticContentAttribute { 223 | didSet { 224 | var superviewIsRTL = false 225 | if let validSuperView = superview?.effectiveUserInterfaceLayoutDirection { superviewIsRTL = validSuperView == .rightToLeft && semanticContentAttribute == .unspecified } 226 | transform.a = semanticContentAttribute == .forceRightToLeft || superviewIsRTL ? -1: 1 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACMonthViewProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthViewProtocols.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | /// The JTAppleCalendarMonthViewDataSource protocol is adopted by an 29 | /// object that mediates the application’™s data model for a 30 | /// the JTAppleCalendarMonthViewDataSource object. data source provides the 31 | /// the calendar-view object with the information it needs to construct and 32 | /// then modify it self 33 | @available(*, unavailable, renamed: "JTACMonthViewDataSource") 34 | public protocol JTAppleCalendarViewDataSource: AnyObject {} 35 | public protocol JTACMonthViewDataSource: AnyObject { 36 | /// Asks the data source to return the start and end boundary dates 37 | /// as well as the calendar to use. You should properly configure 38 | /// your calendar at this point. 39 | /// - Parameters: 40 | /// - calendar: The JTAppleCalendar view requesting this information. 41 | /// - returns: 42 | /// - ConfigurationParameters instance: 43 | func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters 44 | } 45 | 46 | /// The delegate of a JTAppleCalendarMonthView object must adopt the 47 | /// JTAppleCalendarMonthViewDelegate protocol Optional methods of the protocol 48 | /// allow the delegate to manage selections, and configure the cells 49 | @available(*, unavailable, renamed: "JTACMonthViewDelegate") 50 | public protocol JTAppleCalendarViewDelegate: AnyObject {} 51 | public protocol JTACMonthViewDelegate: AnyObject { 52 | /// Asks the delegate if selecting the date-cell with a specified date is 53 | /// allowed 54 | /// - Parameters: 55 | /// - calendar: The JTAppleCalendar view requesting this information. 56 | /// - date: The date attached to the date-cell. 57 | /// - cell: The date-cell view. This can be customized at this point. 58 | /// - cellState: The month the date-cell belongs to. 59 | /// - returns: A Bool value indicating if the operation can be done. 60 | func calendar(_ calendar: JTACMonthView, shouldSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool 61 | 62 | /// Asks the delegate if de-selecting the 63 | /// date-cell with a specified date is allowed 64 | /// - Parameters: 65 | /// - calendar: The JTAppleCalendar view requesting this information. 66 | /// - date: The date attached to the date-cell. 67 | /// - cell: The date-cell view. This can be customized at this point. 68 | /// - cellState: The month the date-cell belongs to. 69 | /// - returns: A Bool value indicating if the operation can be done. 70 | func calendar(_ calendar: JTACMonthView, shouldDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool 71 | 72 | /// Tells the delegate that a date-cell with a specified date was selected 73 | /// - Parameters: 74 | /// - calendar: The JTAppleCalendar view giving this information. 75 | /// - date: The date attached to the date-cell. 76 | /// - cell: The date-cell view. This can be customized at this point. 77 | /// This may be nil if the selected cell is off the screen 78 | /// - cellState: The month the date-cell belongs to. 79 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 80 | 81 | /// Tells the delegate that a date-cell 82 | /// with a specified date was de-selected 83 | /// - Parameters: 84 | /// - calendar: The JTAppleCalendar view giving this information. 85 | /// - date: The date attached to the date-cell. 86 | /// - cell: The date-cell view. This can be customized at this point. 87 | /// This may be nil if the selected cell is off the screen 88 | /// - cellState: The month the date-cell belongs to. 89 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 90 | 91 | /// Tells the delegate that the item at the specified index path was highlighted. 92 | /// - Parameters: 93 | /// - calendar: The JTAppleCalendar view giving this information. 94 | /// - date: The date attached to the date-cell. 95 | /// - cellState: The month the date-cell belongs to. 96 | func calendar(_ calendar: JTACMonthView, didHighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 97 | 98 | /// Tells the delegate that the item at the specified index path was un-highlighted. 99 | /// - Parameters: 100 | /// - calendar: The JTAppleCalendar view giving this information. 101 | /// - date: The date attached to the date-cell. 102 | /// - cellState: The month the date-cell belongs to. 103 | func calendar(_ calendar: JTACMonthView, didUnhighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 104 | 105 | /// Tells the delegate that the JTAppleCalendar view 106 | /// scrolled to a segment beginning and ending with a particular date 107 | /// - Parameters: 108 | /// - calendar: The JTAppleCalendar view giving this information. 109 | /// - startDate: The date at the start of the segment. 110 | /// - endDate: The date at the end of the segment. 111 | func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) 112 | 113 | /// Tells the delegate that the JTAppleCalendar view 114 | /// will scroll to a segment beginning and ending with a particular date 115 | /// - Parameters: 116 | /// - calendar: The JTAppleCalendar view giving this information. 117 | /// - startDate: The date at the start of the segment. 118 | /// - endDate: The date at the end of the segment. 119 | func calendar(_ calendar: JTACMonthView, willScrollToDateSegmentWith visibleDates: DateSegmentInfo) 120 | 121 | /// Tells the delegate that the JTAppleCalendar is about to display 122 | /// a date-cell. This is the point of customization for your date cells 123 | /// - Parameters: 124 | /// - calendar: The JTAppleCalendar view giving this information. 125 | /// - date: The date attached to the cell. 126 | /// - cellState: The month the date-cell belongs to. 127 | /// - indexPath: use this value when dequeing cells 128 | func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell 129 | 130 | /// Tells the delegate that the JTAppleCalendar is about to 131 | /// display a header. This is the point of customization for your headers 132 | /// - Parameters: 133 | /// - calendar: The JTAppleCalendar view giving this information. 134 | /// - date: The date attached to the header. 135 | /// - indexPath: use this value when dequeing cells 136 | func calendar(_ calendar: JTACMonthView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTACMonthReusableView 137 | 138 | /// Informs the delegate that the user just lifted their finger from swiping the calendar 139 | func scrollDidEndDecelerating(for calendar: JTACMonthView) 140 | 141 | /// Tells the delegate that a scroll occured 142 | func calendarDidScroll(_ calendar: JTACMonthView) 143 | 144 | /// Called to retrieve the size to be used for the month headers 145 | func calendarSizeForMonths(_ calendar: JTACMonthView?) -> MonthSize? 146 | 147 | /// Implement the function to configure calendar cells. The code that will go in here is the same 148 | /// that you will code for your cellForItem function. This function is only called to address 149 | /// inconsistencies in the visual appearance as stated by Apple: https://developer.apple.com/documentation/uikit/uicollectionview/1771771-prefetchingenabled 150 | /// a date-cell. This is the point of customization for your date cells 151 | /// - Parameters: 152 | /// - calendar: The JTAppleCalendar view giving this information. 153 | /// - cell: The cell 154 | /// - date: date attached to the cell 155 | /// - cellState: The month the date-cell belongs to. 156 | /// - indexPath: use this value when dequeing cells 157 | func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) 158 | 159 | /// Called to retrieve the size to be used for decoration views 160 | func sizeOfDecorationView(indexPath: IndexPath) -> CGRect 161 | 162 | /// Called in case of tvOS 163 | func indexPathForPreferredFocusedView(in: UICollectionView) -> IndexPath? 164 | } 165 | 166 | /// Default delegate functions 167 | public extension JTACMonthViewDelegate { 168 | func calendar(_ calendar: JTACMonthView, shouldSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool { return true } 169 | func calendar(_ calendar: JTACMonthView, shouldDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool { return true } 170 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {} 171 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {} 172 | func calendar(_ calendar: JTACMonthView, didHighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {} 173 | func calendar(_ calendar: JTACMonthView, didUnhighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) {} 174 | func calendar(_ calendar: JTACMonthView, willScrollToDateSegmentWith visibleDates: DateSegmentInfo) {} 175 | func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) {} 176 | func calendar(_ calendar: JTACMonthView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTACMonthReusableView { 177 | assert(false, "You have implemted a header size function, but forgot to implement the `headerViewForDateRange` function") 178 | return JTACMonthReusableView() 179 | } 180 | func calendarDidScroll(_ calendar: JTACMonthView) {} 181 | func calendarSizeForMonths(_ calendar: JTACMonthView?) -> MonthSize? { return nil } 182 | func sizeOfDecorationView(indexPath: IndexPath) -> CGRect { return .zero } 183 | func scrollDidEndDecelerating(for calendar: JTACMonthView) {} 184 | func indexPathForPreferredFocusedView(in: UICollectionView) -> IndexPath? { return nil } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACVariables.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACVariables.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | // Calculated Variables 29 | extension JTACMonthView { 30 | /// Workaround for Xcode bug that prevents you from connecting the delegate in the storyboard. 31 | /// Remove this extra property once Xcode gets fixed. 32 | @IBOutlet public var ibCalendarDelegate: AnyObject? { 33 | get { return calendarDelegate as AnyObject? } 34 | set { 35 | if (newValue != nil) { 36 | assert(newValue as? JTACMonthViewDelegate != nil, "Error, your delegate is not of type JTACMonthViewDelegate.") 37 | } 38 | calendarDelegate = newValue as? JTACMonthViewDelegate 39 | } 40 | } 41 | 42 | /// Workaround for Xcode bug that prevents you from connecting the delegate in the storyboard. 43 | /// Remove this extra property once Xcode gets fixed. 44 | @IBOutlet public var ibCalendarDataSource: AnyObject? { 45 | get { return calendarDataSource as AnyObject? } 46 | set { 47 | if (newValue != nil) { 48 | assert(newValue as? JTACMonthViewDataSource != nil, "Error, your dataSource is not of type JTACMonthViewDataSource.") 49 | } 50 | calendarDataSource = newValue as? JTACMonthViewDataSource 51 | } 52 | } 53 | 54 | @available(*, unavailable) 55 | /// Will not be used by subclasses 56 | open override var delegate: UICollectionViewDelegate? { 57 | get { return super.delegate } 58 | set { /* Do nothing */ } 59 | } 60 | 61 | @available(*, unavailable) 62 | /// Will not be used by subclasses 63 | open override var dataSource: UICollectionViewDataSource? { 64 | get { return super.dataSource } 65 | set {/* Do nothing */ } 66 | } 67 | 68 | /// Returns all selected dates 69 | public var selectedDates: [Date] { 70 | return selectedDatesSet.sorted() 71 | } 72 | 73 | var selectedDatesSet: Set { 74 | return Set(selectedCellData.values.map { $0.date }) 75 | } 76 | 77 | var monthInfo: [Month] { 78 | get { return theData.months } 79 | set { theData.months = newValue } 80 | } 81 | 82 | var numberOfMonths: Int { 83 | return monthInfo.count 84 | } 85 | 86 | var totalDays: Int { 87 | return theData.totalDays 88 | } 89 | 90 | var calendarViewLayout: JTACMonthLayout { 91 | guard let layout = collectionViewLayout as? JTACMonthLayout else { 92 | developerError(string: "Calendar layout is not of type JTAppleCalendarMonthLayout.") 93 | return JTACMonthLayout(withDelegate: self) 94 | } 95 | return layout 96 | } 97 | 98 | var functionIsUnsafeSafeToRun: Bool { 99 | return !calendarLayoutIsLoaded || isScrollInProgress || isReloadDataInProgress 100 | } 101 | 102 | var calendarLayoutIsLoaded: Bool { return calendarViewLayout.isCalendarLayoutLoaded } 103 | var startDateCache: Date { 104 | guard let date = _cachedConfiguration?.startDate else { 105 | assert(false, "Attemped to access startDate when Datasource/delegate is not set yet. Returning todays's date") 106 | return Date() 107 | } 108 | return date 109 | } 110 | var endDateCache: Date { 111 | guard let date = _cachedConfiguration?.endDate else { 112 | assert(false, "Attemped to access endDate when Datasource/delegate is not set yet. Returning todays's date") 113 | return Date() 114 | } 115 | return date 116 | } 117 | var calendar: Calendar { 118 | guard let calendar = _cachedConfiguration?.calendar else { 119 | assert(false, "Attemped to access calendar when Datasource/delegate is not set yet. Returning default value") 120 | return Calendar.current 121 | } 122 | return calendar 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACYearView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTAppleCalendarYearView.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | open class JTACYearView: UICollectionView { 29 | var configurationParameters = ConfigurationParameters(startDate: Date(), endDate: Date()) 30 | var monthData: [Any] = [] 31 | 32 | 33 | /// The object that acts as the delegate of the calendar year view. 34 | weak open var calendarDelegate: JTACYearViewDelegate? 35 | weak open var calendarDataSource: JTACYearViewDataSource? { 36 | didSet { setupYearViewCalendar() } 37 | } 38 | 39 | /// Workaround for Xcode bug that prevents you from connecting the delegate in the storyboard. 40 | /// Remove this extra property once Xcode gets fixed. 41 | @IBOutlet public var ibCalendarDelegate: AnyObject? { 42 | get { return calendarDelegate } 43 | set { calendarDelegate = newValue as? JTACYearViewDelegate } 44 | } 45 | 46 | /// Workaround for Xcode bug that prevents you from connecting the delegate in the storyboard. 47 | /// Remove this extra property once Xcode gets fixed. 48 | @IBOutlet public var ibCalendarDataSource: AnyObject? { 49 | get { return calendarDataSource } 50 | set { calendarDataSource = newValue as? JTACYearViewDataSource } 51 | } 52 | 53 | open func dataSourcefrom(configurationParameters: ConfigurationParameters) -> [Any] { 54 | return JTAppleDateConfigGenerator.shared.setupMonthInfoDataForStartAndEndDate(configurationParameters).months 55 | } 56 | 57 | func setupYearViewCalendar() { 58 | guard let validConfig = calendarDataSource?.configureCalendar(self) else { 59 | print("Invalid datasource") 60 | return; 61 | } 62 | 63 | configurationParameters = validConfig.configurationParameters 64 | monthData = validConfig.months 65 | dataSource = self 66 | delegate = self 67 | } 68 | 69 | } 70 | 71 | extension JTACYearView: JTACCellMonthViewDelegate { 72 | public func monthView(_ monthView: JTACCellMonthView, drawingFor segmentRect: CGRect, with date: Date, dateOwner: DateOwner, monthIndex: Int) { 73 | calendarDelegate?.calendar(self, monthView: monthView, drawingFor: segmentRect, with: date, dateOwner: dateOwner, monthIndex: monthIndex) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTACYearViewProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JTACYearViewProtocols.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import UIKit 27 | 28 | public protocol JTACYearViewDelegate: AnyObject { 29 | func calendar(_ calendar: JTACYearView, cellFor item: Any, at date: Date, indexPath: IndexPath) -> JTACMonthCell 30 | func calendar(_ calendar: JTACYearView, 31 | monthView: JTACCellMonthView, 32 | drawingFor segmentRect: CGRect, 33 | with date: Date, 34 | dateOwner: DateOwner, 35 | monthIndex index: Int) 36 | func calendar(_ calendar: JTACYearView, sizeFor item: Any) -> CGSize 37 | } 38 | 39 | extension JTACYearViewDelegate { 40 | func calendar(_ calendar: JTACYearView, 41 | monthView: JTACCellMonthView, 42 | drawingFor segmentRect: CGRect, 43 | with date: Date, 44 | dateOwner: DateOwner, 45 | monthIndex index: Int){} 46 | func calendar(_ calendar: JTACYearView, sizeFor item: Any) -> CGSize { return .zero } 47 | } 48 | 49 | public protocol JTACYearViewDataSource: AnyObject { 50 | func configureCalendar(_ calendar: JTACYearView) -> (configurationParameters: ConfigurationParameters, months: [Any]) 51 | } 52 | 53 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/JTAppleCalendar.h: -------------------------------------------------------------------------------- 1 | // 2 | // JTACMonthView.swift 3 | // 4 | // Copyright (c) 2016-2020 JTAppleCalendar (https://github.com/patchthecode/JTAppleCalendar) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | #import 26 | 27 | //! Project version number for JTAppleCalendar. 28 | FOUNDATION_EXPORT double JTAppleCalendarVersionNumber; 29 | 30 | //! Project version string for JTAppleCalendar. 31 | FOUNDATION_EXPORT const unsigned char JTAppleCalendarVersionString[]; 32 | 33 | // In this header, you should import all the public headers of your framework using statements like #import 34 | 35 | 36 | -------------------------------------------------------------------------------- /Sources/JTAppleCalendar/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTrackingDomains 6 | 7 | NSPrivacyCollectedDataTypes 8 | 9 | NSPrivacyAccessedAPITypes 10 | 11 | NSPrivacyTracking 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Tests/JTAppleCalendarTests/JTAppleCalendarTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import JTAppleCalendar 3 | 4 | final class JTAppleCalendarTests: XCTestCase { 5 | func testExample() { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | // XCTAssertEqual(JTAppleCalendar().text, "Hello, World!") 10 | } 11 | 12 | static var allTests = [ 13 | ("testExample", testExample), 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Tests/JTAppleCalendarTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(JTAppleCalendarTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import JTAppleCalendarTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += JTAppleCalendarTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /docs/adding-events/Adding Events.md: -------------------------------------------------------------------------------- 1 | # Adding Events 2 | 3 | > [!WARNING] 4 | > This documentation requires assistance, particularly updating to SwiftUI. If possible, please submit a PR to help improve the documentation 5 | 6 | Adding events – or adding a dot-view to signify an event on a date cell – is exactly the same as you would add a custom view to a UITableView or UICollectionView cell. 7 | 8 | In the case of UICollectionView/UITableView, you would design the cell in the cellForItemAtIndexPath function based on the cell’s index, in relation the the dataSource’s index. 9 | 10 | In the case of this library, it is exactly the same. The only difference is that instead of using a cell’s associated index, we use a cell’s associated date. 11 | 12 | # Creating the dot view 13 | 14 | First, for better viewing, increase the constraint height of the calendar to 450. 15 | 16 | ![increase height of container](./images/image1.png) 17 | 18 | Next, create the red `dotView` as seen below. Take note of the constraints used. 19 | 20 | ![create red dotView](./images/image2.png) 21 | 22 | The dotView needs to look round, therefore set a cornerRadius for it. I use storyboard for simplicity, but code may be preferred. 23 | 24 | ![create cornerRadius](./images/image3.png) 25 | 26 | Add an IBOutlet for the the dotView. Go to the DateCell class and add this code. 27 | 28 | ```swift 29 | import JTAppleCalendar 30 | import UIKit 31 | 32 | class DateCell: JTAppleCell { 33 | @IBOutlet var dateLabel: UILabel! 34 | @IBOutlet var dotView: UIView! 35 | } 36 | ``` 37 | 38 | Now connect your dotView IBOutlet on storyboard as seen below. 39 | 40 | ![connect IBOutlet](./images/image4.png) 41 | 42 | Run your app. It should look like this 43 | 44 | ![dots on calendar view](./images/image5.png) 45 | 46 | ## Attaching events to your dotView 47 | 48 | Now we only want the dot view to be visible for specific dates in our dataSource. The calendar library operates on dates and not indexes, therefore the dataSource should relate every cell to dates. 49 | 50 | Lets add a DateFormatter to be used globally for events & the dataSource dictionary. DataSource will be explained below. 51 | 52 | ```swift 53 | class ViewController: UIViewController { 54 | @IBOutlet var calendarView: JTAppleCalendarView! 55 | 56 | var calendarDataSource: [String:String] = [:] 57 | var formatter: DateFormatter { 58 | let formatter = DateFormatter() 59 | formatter.dateFormat = "dd-MMM-yyyy" 60 | return formatter 61 | } 62 | ``` 63 | 64 | Modify the `configureCalendar` function 65 | 66 | ```swift 67 | extension ViewController: JTAppleCalendarViewDataSource { 68 | func configureCalendar(_ calendar: JTAppleCalendarView) ->; ConfigurationParameters { 69 | let startDate = formatter.date(from: "01-jan-2018")! 70 | let endDate = Date() 71 | return ConfigurationParameters(startDate: startDate, endDate: endDate) 72 | } 73 | } 74 | ``` 75 | 76 | Add this line of code 77 | 78 | ```swift 79 | func configureCell(view: JTAppleCell?, cellState: CellState) { 80 | guard let cell = view as? DateCell else { return } 81 | cell.dateLabel.text = cellState.text 82 | handleCellTextColor(cell: cell, cellState: cellState) 83 | handleCellEvents(cell: cell, cellState: cellState) 84 | } 85 | ``` 86 | 87 | ## The dataSource 88 | 89 | Finally, let’s talk about population of the dataSource. Add the following code. 90 | 91 | ```swift 92 | func populateDataSource() { 93 | // You can get the data from a server. 94 | // Then convert that data into a form that can be used by the calendar. 95 | calendarDataSource = [ 96 | "07-Jan-2018": "SomeData", 97 | "15-Jan-2018": "SomeMoreData", 98 | "15-Feb-2018": "MoreData", 99 | "21-Feb-2018": "onlyData", 100 | ] 101 | // update the calendar 102 | calendarView.reloadData() 103 | } 104 | ``` 105 | 106 | We need some way to associate the dateCells with the datasource. The best way to identify a cell is by Date, therefore I chose a dataSource with a date string as the key. 107 | 108 | Now create the following function. It is responsible for determining the visibility of the dotView. If the date has an event, it will be visible, else it will be hidden. 109 | 110 | ```swift 111 | func handleCellEvents(cell: DateCell, cellState: CellState) { 112 | let dateString = formatter.string(from: cellState.date) 113 | if calendarDataSource[dateString] == nil { 114 | cell.dotView.isHidden = true 115 | } else { 116 | cell.dotView.isHidden = false 117 | } 118 | } 119 | ``` 120 | 121 | To test this code, finally add this line of code and run the app. 122 | 123 | ```swift 124 | override func viewDidLoad() { 125 | super.viewDidLoad() 126 | calendarView.scrollDirection = .horizontal 127 | calendarView.scrollingMode = .stopAtEachCalendarFrame 128 | calendarView.showsHorizontalScrollIndicator = false 129 | 130 | populateDataSource() 131 | } 132 | ``` 133 | 134 | ## Next Steps 135 | 136 | Learn more about [implementing week numbers](../implementing-week-numbers/Implementing%20week%20numbers.md) 137 | -------------------------------------------------------------------------------- /docs/adding-events/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/adding-events/images/image1.png -------------------------------------------------------------------------------- /docs/adding-events/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/adding-events/images/image2.png -------------------------------------------------------------------------------- /docs/adding-events/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/adding-events/images/image3.png -------------------------------------------------------------------------------- /docs/adding-events/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/adding-events/images/image4.png -------------------------------------------------------------------------------- /docs/adding-events/images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/adding-events/images/image5.png -------------------------------------------------------------------------------- /docs/build-calendar/Build A Calendar From Scratch.md: -------------------------------------------------------------------------------- 1 | # Build a Calendar from scratch 2 | 3 | ## HELP REQUIRED - Using SwiftUI 4 | 5 | > [!WARNING] 6 | > This section is incomplete, please help update it by submitting a PR 7 | 8 | ## Using story board 9 | 10 | 1. Drag a UICollectionView unto the screen. Change its class and module to JTAppleCalendarView and JTAppleCalendar respectively. 11 | 12 | Correctly set both the height and width constraints. Constraints are needed for the library to determine cell size. 13 | 14 | ![constraints](./images/image1.png) 15 | 16 | 2. UICollectionView may come with default minimum-cell-spacing and minimum-line-spacing. Unless your design requires it, please set both to zero as shown below. 17 | 18 | ![constraints](./images/image2.png) 19 | 20 | 3. Design the cell. This example uses a single UILabel. But this design can be anything (example images, rounded selections, dot-views for events etc). 21 | 22 | Be sure to set correct constraints for the label (typically center the label in the cell both vertically and horizontally). Improper constraints results in bugged views. Also remember to set the cell’s `reusableIdentifier` to `dateCell`. 23 | 24 | ![dateCell](./images/image3.png) 25 | 26 | 4. Create a new cell class. 27 | 28 | ```swift 29 | import JTAppleCalendar 30 | import UIKit 31 | class DateCell: JTAppleCell { 32 | @IBOutlet var dateLabel: UILabel! 33 | } 34 | ``` 35 | 36 | Now head back to Storyboard. The cell’s class is already set, so just connect the IBOutlet to the UILabel of the cell. 37 | 38 | ![IBOutlet](./images/image4.png) 39 | ![IBOutlet](./images/image5.png) 40 | 41 | 5. Now lets write some more code. 42 | 43 | The following is self explanatory. 44 | 45 | ```swift 46 | import UIKit 47 | import JTAppleCalendar 48 | class ViewController: UIViewController { 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | } 52 | } 53 | ``` 54 | 55 | 6. Go back to Storyboard and set the calendar’s ibCalendarDataSource and ibCalendarDelegate to be the ViewController subclass. 56 | 57 | ![IBOutlet](./images/image6.png) 58 | 59 | Write some more code. 60 | 61 | This is the first required delegate. 62 | 63 | ```swift 64 | extension ViewController: JTAppleCalendarViewDataSource { 65 | func configureCalendar(\_ calendar: JTAppleCalendarView) -> ConfigurationParameters { 66 | let formatter = DateFormatter() 67 | formatter.dateFormat = "yyyy MM dd" 68 | let startDate = formatter.date(from: "2018 01 01")! 69 | let endDate = Date() 70 | return ConfigurationParameters(startDate: startDate, endDate: endDate) 71 | } 72 | } 73 | ``` 74 | 75 | This library is a ranged calendar. It has no infinite scrolling out-of-the-box (although some creative developers have accomplished it). There are 2 mandatory parameters, `startDate` for `startMonth`, and `endDate` for `endMonth`. 76 | 77 | ConfigurationParameters full parameter list is: 78 | 79 | - **startDate** – start boundary for calendar 80 | - **endDate** – end boundary for calendar 81 | - **numberOfRows** – default is 6 82 | - **calendar** – this calendar() instance is responsible for region/timezones and the way your calendar will look in case you want an Arabic calendar for example. If none is given, the default iOS Calendar instance is provided. 83 | - **generateInDates** – control the generation of inDates. 84 | - **generateOutDates** – control the generation of outDates. 85 | - **firstDayOfWeek** – set any day to be the first day of the week. Sunday is default. 86 | - **hasStrictBoundaries** – controls the strictness of month boundaries. 87 | 88 | These are the final 2 delegate functions. 89 | 90 | ```swift 91 | extension ViewController: JTAppleCalendarViewDelegate { 92 | func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell { 93 | let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "dateCell", for: indexPath) as! DateCell 94 | cell.dateLabel.text = cellState.text 95 | return cell 96 | } 97 | func calendar(_ calendar: JTAppleCalendarView, willDisplay cell: JTAppleCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 98 | let cell = cell as! DateCell 99 | cell.dateLabel.text = cellState.text 100 | } 101 | } 102 | ``` 103 | 104 | These 2 functions should contain the same code, therefore it is wise to have a shared function to reduce code duplication. The only difference between these two functions should be the first line of code (the dequeuing code). Reasons for the 2 functions having the same code are found [here](https://github.com/patchthecode/JTAppleCalendar/issues/553) under problem#1. 105 | 106 | Now run your test app and you should have a simple calendar going. If you wish to have paging enabled or have the scrollBar be invisible etc etc, then just remember that this library is a UICollectionView. Therefore enabling and hiding these are done the exact same way as a UICollectionView. 107 | 108 | ## Next Steps 109 | 110 | Where to go from here? Learn about [common elements](../common-elements/Common%20Elements.md) 111 | -------------------------------------------------------------------------------- /docs/build-calendar/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image1.png -------------------------------------------------------------------------------- /docs/build-calendar/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image2.png -------------------------------------------------------------------------------- /docs/build-calendar/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image3.png -------------------------------------------------------------------------------- /docs/build-calendar/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image4.png -------------------------------------------------------------------------------- /docs/build-calendar/images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image5.png -------------------------------------------------------------------------------- /docs/build-calendar/images/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/build-calendar/images/image6.png -------------------------------------------------------------------------------- /docs/common-elements/Common Elements.md: -------------------------------------------------------------------------------- 1 | # Common elements of every calendar 2 | 3 | > [!WARNING] 4 | > This section requires assistance. Please submit a PR if possible to help improve documentation 5 | 6 | Each calendar has a set of common elements. Configuration for these elements can be found here: 7 | 8 | - [Configuring inDates/monthDates/outDates](./configure-in-out-month-dates/Configuring%20inDates%20monthDates%20outDates.md) 9 | - [Regular selection](./regular-selection/Regular%20Selection.md) 10 | - [Handle device rotation](./device-rotation/Handling%20Device%20Rotation.md) 11 | 12 | # Next Steps 13 | 14 | Learn more about [scrolling modes](../scrolling-modes/Scrolling%20Modes.md) 15 | -------------------------------------------------------------------------------- /docs/common-elements/configure-in-out-month-dates/Configuring inDates monthDates outDates.md: -------------------------------------------------------------------------------- 1 | # Configuring inDates monthDates outDates 2 | 3 | > [!WARNING] 4 | > This documentation requires assistance, particularly updating to SwiftUI. If possible, please submit a PR to help improve the documentation 5 | 6 | If you followed the [building from scratch tutorial](../../build-calendar/Build%20A%20Calendar%20From%20Scratch.md), your calendar should look like this. You would also know what inDates/outDates are. 7 | 8 | ![calendar built from scratch](./images/image1.png) 9 | 10 | ## Set fixed inDates/outDates/monthDates 11 | 12 | The calendar is difficult to read with inDates/outDates/monthDates the same color. Lets change that. Create the following 2 functions. 13 | 14 | ```swift 15 | func configureCell(view: JTAppleCell?, cellState: CellState) { 16 | guard let cell = view as? DateCell else { return } 17 | cell.dateLabel.text = cellState.text 18 | handleCellTextColor(cell: cell, cellState: cellState) 19 | } 20 | 21 | func handleCellTextColor(cell: DateCell, cellState: CellState) { 22 | if cellState.dateBelongsTo == .thisMonth { 23 | cell.dateLabel.textColor = UIColor.black 24 | } else { 25 | cell.dateLabel.textColor = UIColor.gray 26 | } 27 | } 28 | ``` 29 | 30 | ![coloured containers of dates](./images/image2.png) 31 | 32 | - Blue indicates `thisMonth` 33 | - Red indicates `previousMonthWithinBoundary` 34 | - Green indicates `followingMonthWithinBoundary` 35 | 36 | Rewrite the following functions 37 | 38 | ```swift 39 | func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell { 40 | let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "dateCell", for: indexPath) as! DateCell 41 | self.calendar(calendar, willDisplay: cell, forItemAt: date, cellState: cellState, indexPath: indexPath) 42 | return cell 43 | } 44 | 45 | func calendar(_ calendar: JTAppleCalendarView, willDisplay cell: JTAppleCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 46 | configureCell(view: cell, cellState: cellState) 47 | } 48 | ``` 49 | 50 | The `cellForItemAtDate` function calls the `willDisplayCell` function so that code can be reused. This is done because both functions have to [contain the same code](https://github.com/patchthecode/JTAppleCalendar/issues/553) 51 | 52 | Finally, get rid of the UIScrollView (either through code or InterfaceBuilder), and enable paging. This is done exactly the same way as would be done for a UICollectionView. 53 | 54 | Now run your app. It should look like this. 55 | 56 | ![greyed out numbers on each end of month](./images/image3.png) 57 | 58 | ## Other design considerations 59 | 60 | ### Hidden inDates/outDates 61 | 62 | Apart from making the changing the color of inDates/outDates you can also make them hidden. 63 | 64 | ```swift 65 | if cellState.dateBelongsTo == .thisMonth { 66 | cell.isHidden = false 67 | } else { 68 | cell.isHidden = true 69 | } 70 | ``` 71 | 72 | To give the following effect 73 | 74 | ![calendar with missing start and end dates](./images/image4.png) 75 | 76 | You can hide inDates, but leave outDates visible or vice-versa. Any combination is possible. This is done by changing the if condition. 77 | 78 | The full list of `dateBelongsTo`: 79 | 80 | - thisMonth 81 | - previousMonthWithinBoundary 82 | - previousMonthOutsideBoundary 83 | - followingMonthWithinBoundary 84 | - followingMonthOutsideBoundary 85 | 86 | ### Generating inDates/outDates. What are they? 87 | 88 | It is important to note that generating in/out dates is different from hiding them as shown above. Hidden ones are still generated. The difference affects how your calendar looks. 89 | 90 | Heading back to the `configureCalendar` delegate introduced in the [calendar from scratch tutorial](../../build-calendar/Build%20A%20Calendar%20From%20Scratch.md). Lets modify the ConfigurationParameters to configure the generation of inDates/outDates 91 | 92 | ```swift 93 | func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters { 94 | let formatter = DateFormatter() 95 | formatter.dateFormat = "yyyy MM dd" 96 | 97 | let startDate = formatter.date(from: "2018 01 01")! 98 | let endDate = Date() 99 | return ConfigurationParameters(startDate: startDate, 100 | endDate: endDate, 101 | generateInDates: .forAllMonths, 102 | generateOutDates: .tillEndOfGrid) 103 | } 104 | ``` 105 | 106 | #### generateInDates 107 | 108 | - `forFirstMonthOnly` – Only your first month will generate inDates/offsets. All the other months will start with no inDates/offsets. 109 | - `forAllMonths` – All months will have inDates/offsets 110 | - `off` – No months will have any inDates/offsets. 111 | 112 | #### generateOutDates 113 | 114 | - `tillEndOfRow` – This will generate outDates till it reaches the first end of a row. In short – if your calendar month has 6 rows, then it will display 6 rows. If your calendar month has 5 rows, then it will display 5 rows. 115 | - `tillEndOfGrid` – This will generate outDates until it reaches the end of a 6 x 7 grid (42 cells). In Short, it will always display a 6 row calendar month. 116 | - `off` – Your calendar month will not generate any outDates. 117 | 118 | ## Next Steps 119 | 120 | Learn more about [regular selection of dates](../regular-selection/Regular%20Selection.md) 121 | -------------------------------------------------------------------------------- /docs/common-elements/configure-in-out-month-dates/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/common-elements/configure-in-out-month-dates/images/image1.png -------------------------------------------------------------------------------- /docs/common-elements/configure-in-out-month-dates/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/common-elements/configure-in-out-month-dates/images/image2.png -------------------------------------------------------------------------------- /docs/common-elements/configure-in-out-month-dates/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/common-elements/configure-in-out-month-dates/images/image3.png -------------------------------------------------------------------------------- /docs/common-elements/configure-in-out-month-dates/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/common-elements/configure-in-out-month-dates/images/image4.png -------------------------------------------------------------------------------- /docs/common-elements/device-rotation/Handling Device Rotation.md: -------------------------------------------------------------------------------- 1 | # Handling device rotation 2 | 3 | Whenever the device orientation changes, you need to let the library know about it by calling this function 4 | 5 | ```swift 6 | public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator, anchorDate: Date?) 7 | ``` 8 | 9 | Typically, developers should add it to the `viewWillTransition` delegate function of their ViewController SubClass. 10 | 11 | Here is an example of the usage: 12 | 13 | ```swift 14 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 15 | let visibleDates = calendarView.visibleDates() 16 | calendarView.viewWillTransition(to: .zero, with: coordinator, anchorDate: visibleDates.monthDates.first?.date) 17 | } 18 | ``` 19 | 20 | The library needs to know what date should be focused on the screen when the orientation changes. In the above code, the first visible `monthDate` was captured and used as the anchor focus date. 21 | 22 | When the orientation change completes, that month’s date will be focused on the screen. 23 | 24 | ## Next Steps 25 | 26 | Learn more about [scrolling modes](../../scrolling-modes/Scrolling%20Modes.md) 27 | -------------------------------------------------------------------------------- /docs/common-elements/regular-selection/Regular Selection.md: -------------------------------------------------------------------------------- 1 | # Regular selection 2 | 3 | > [!WARNING] 4 | > This section requires assistance, particularly updating to SwiftUI. Please submit a PR if possible to help improve documentation 5 | 6 | When user taps a date cell, you’ll need a highlighted UIView to show the user that the cell was selected. 7 | 8 | We need to create a `selectedView` in the DateCell class. We’ll use this to display a red-color selection to the user. 9 | 10 | Modify the DateCell to include the highlighted code below 11 | 12 | ```swift 13 | class DateCell: JTAppleCell { 14 | @IBOutlet var dateLabel: UILabel! 15 | @IBOutlet var selectedView: UIView! 16 | } 17 | ``` 18 | 19 | On storyboard, create a UIView and place it behind the UILabel so that it will not hide the label. Set color to red. It is important that you set valid constraints else it will not display correctly. I have set both width and height to be 50. I have also set the view to be centered both vertical and horizontal inside the cell. 20 | 21 | ![storyboard](./images/image1.png) 22 | 23 | Next, connect this UIView to the `selectedView` IBOutlet variable in the DateCell class. The `selectedView` should now be connected to the cell. 24 | 25 | Next, Add the following highlighted code to the ViewController class 26 | 27 | ```swift 28 | func configureCell(view: JTAppleCell?, cellState: CellState) { 29 | guard let cell = view as? DateCell else { return } 30 | cell.dateLabel.text = cellState.text 31 | handleCellTextColor(cell: cell, cellState: cellState) 32 | handleCellSelected(cell: cell, cellState: cellState) 33 | } 34 | 35 | func handleCellSelected(cell: DateCell, cellState: CellState) { 36 | if cellState.isSelected { 37 | cell.selectedView.layer.cornerRadius = 13 38 | cell.selectedView.isHidden = false 39 | } else { 40 | cell.selectedView.isHidden = true 41 | } 42 | } 43 | ``` 44 | 45 | The code is now ready to handle both selections and de-selections. 46 | 47 | The final step is to call this function both when a cell is selected and deselected. Add the following code to the ViewController class. 48 | 49 | ```swift 50 | func calendar(\_ calendar: JTAppleCalendarView, didSelectDate date: Date, cell: JTAppleCell?, cellState: CellState) { 51 | configureCell(view: cell, cellState: cellState) 52 | } 53 | 54 | func calendar(\_ calendar: JTAppleCalendarView, didDeselectDate date: Date, cell: JTAppleCell?, cellState: CellState) { 55 | configureCell(view: cell, cellState: cellState) 56 | } 57 | ``` 58 | 59 | Other delegate functions that may be of interest are: 60 | 61 | ```swift 62 | func calendar(\_ calendar: JTAppleCalendarView, shouldSelectDate date: Date, cell: JTAppleCell?, cellState: CellState) -> Bool { 63 | return true // Based on a criteria, return true or false 64 | } 65 | ``` 66 | 67 | Very useful if you want to prevent selection. 68 | 69 | ## Next Steps 70 | 71 | Learn more about [device rotation](../device-rotation/Handling%20Device%20Rotation.md) 72 | -------------------------------------------------------------------------------- /docs/common-elements/regular-selection/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/common-elements/regular-selection/images/image1.png -------------------------------------------------------------------------------- /docs/get-started/Get Started.md: -------------------------------------------------------------------------------- 1 | # JTAppleCalendar Tutorials and Examples [V 7.1.7] 2 | 3 | > [!CAUTION] 4 | > This documentation comes from [this zipped version of the old docs site](https://github.com/patchthecode/JTAppleCalendar/issues/1397#issuecomment-2054113374) in the repo, and may be out of date with respect to the latest changes. Please submit an issue (or better yet a PR) to resolve any issues in the documentation 5 | 6 | ## [Version 8.0.0 migration guide](../migration-guide/v8%20Migration%20Guide.md) 7 | 8 | ## Why use this library? 9 | 10 | ![alt text](./image1.gif) 11 | 12 | - **Total customization of all views** – This lib does not design anything. It only provides a layout (7 columns and 1-6 rows). Therefore any visual design of both cells and calendar is possible. 13 | - **Horizontal or vertical mode** – Ability to switch from a vertical scrolling to horizontal scrolling calendar. 14 | - **Range selection** – Select dates in a range. 15 | - **Week/Month mode** – 7 columns (Sunday to Saturday). But you decide how many rows to display. Number of rows are limited to the range 1 to 6. 16 | - **Custom first day of week** – First day of week does not have to be Sunday. You can pick any day. 17 | - Headers – Ability to add headers of varying sizes for each month. 18 | 19 | These are a fraction of many more features. Here are [calendar styles](https://github.com/patchthecode/JTAppleCalendar/issues/2) created by developers using this lib. 20 | 21 | ## Repetitive questions / delayed answers 22 | 23 | Familiarity with the following non-calendar pointers, helps avoid repetitive questions and delayed answers. 24 | 25 | - How to use UITableView or UICollectionView and understand how delegate functions work. Knowledge that cells are reused in these controls. 26 | - Questions about this library asked on Github, will get a faster response than contacting me by email. 27 | - Knowledge about the iOS Calendar() class and how time zones work in iOS to avoid questions such as [this one](https://github.com/patchthecode/JTAppleCalendar/issues/252) (2nd most repeated question) 28 | 29 | Lets Begin! 30 | 31 | 1. [Installation](../installation/Installation.md) 32 | 2. [Build calendar from scratch](../build-calendar/Build%20A%20Calendar%20From%20Scratch.md) 33 | 3. [Common elements of every calendar](../common-elements/Common%20Elements.md) 34 | - [Configuring inDates/monthDates/outDates](../common-elements/configure-in-out-month-dates/Configuring%20inDates%20monthDates%20outDates.md) 35 | - [Regular selection](../common-elements//regular-selection/Regular%20Selection.md) 36 | - [Handle device rotation](../common-elements/device-rotation/Handling%20Device%20Rotation.md) 37 | 4. [Scrolling modes](../scrolling-modes/Scrolling%20Modes.md) 38 | 5. [Switch between month-view and week-view](../switch-month-to-week-view/Switch%20between%20month-view%20and%20week-view.md) 39 | 6. [Headers](../headers/Headers.md) 40 | 7. [Range selection styles](../range-selection-styles/Range%20selection%20styles.md) 41 | 8. [Events and loading information from a server](../adding-events/Adding%20Events.md) 42 | 9. [How to add a week number column](../implementing-week-numbers/Implementing%20week%20numbers.md) 43 | -------------------------------------------------------------------------------- /docs/get-started/image1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/get-started/image1.gif -------------------------------------------------------------------------------- /docs/headers/Headers.md: -------------------------------------------------------------------------------- 1 | # Headers 2 | 3 | ## Outside Headers 4 | 5 | The easiest header you can create is with a UIStackView containing 7 UILabels for the days and another label for the month. 6 | 7 | ![easiest header](./images/image1.png) 8 | 9 | These headers will not scroll with calendar-view (seen below) and have nothing to do with the calendar as they are created by yourself. 10 | 11 | ![non scrolling header](./images/image2.gif) 12 | 13 | ## Inside Headers 14 | 15 | These are generated by the calendar. They will scroll as the user scrolls. 16 | 17 | ![scrolling headers](./images/image3.gif) 18 | 19 | > [!WARNING] 20 | > This documentation requires assistance getting up to date, especially with SwiftUI. Please submit a PR if possible to help out 21 | 22 | ### Step 1 23 | 24 | We will use storyboard mixed with some code, but this can also be done with xibs or pure code only. 25 | 26 | Let’s make the calendar both horizontally scrolled and paged. To do that we need a calendar IBOutlet. Add the following to the ViewController class. 27 | 28 | ```swift 29 | class ViewController: UIViewController { 30 | @IBOutlet var calendarView: JTAppleCalendarView! 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | } 34 | } 35 | ``` 36 | 37 | Next, let’s connect this outlet with the calendar on storyboard. 38 | 39 | ![connect outlet to calendar](./images/image4.png) 40 | 41 | Now, write the following code to make the calendar horizontal and paged. Keep in mind that everything in code can also be done direct via xibs/storyboard. 42 | 43 | ```swift 44 | class ViewController: UIViewController { 45 | @IBOutlet var calendarView: JTAppleCalendarView! 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | calendarView.scrollDirection = .horizontal 49 | calendarView.scrollingMode = .stopAtEachCalendarFrame 50 | calendarView.showsHorizontalScrollIndicator = false 51 | } 52 | } 53 | ``` 54 | 55 | If you run your project, the calendar should look better visually. 56 | 57 | Now head back to storyboard, and let’s create the header. 58 | 59 | ### Step 2 60 | 61 | In storyboard, click on the CollectionView and add the section header. Set the color to green and then add a UILabel. Using constraints, center the UILabel both vertically and horizontally inside of the green header. 62 | 63 | ![center UILabel](./images/image5.png) 64 | 65 | Note, these parts should already be familiar to you because it is exactly the same as a regular UICollectionView. 66 | 67 | Set the header’s reusable identifier to `DateHeader` 68 | 69 | ![set to DateHeader](./images/image6.png) 70 | 71 | ### Step 3 72 | 73 | Now we need to create a class for the header, then set it on storyboard. Create the following class. 74 | 75 | ```swift 76 | import UIKit 77 | import JTAppleCalendar 78 | 79 | class DateHeader: JTAppleCollectionReusableView { 80 | @IBOutlet var monthTitle: UILabel! 81 | } 82 | ``` 83 | 84 | Head to storyboard and set the header’s class to be `DateHeader` 85 | 86 | ![set class to DateHeader](./images/image6.png) 87 | 88 | Now connect the IBOutlet `monthTitle` to the UILabel within the header on storyboard. 89 | 90 | ![connect IBOutlet](./images/image6.png) 91 | 92 | 4. Finally, let’s write the header delegates. You notice this entire process is almost exactly the way headers are done in a UICollectionView. 93 | 94 | ```swift 95 | func calendar(\_ calendar: JTAppleCalendarView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTAppleCollectionReusableView { 96 | let formatter = DateFormatter() // Declare this outside, to avoid instancing this heavy class multiple times. 97 | formatter.dateFormat = "MMM" 98 | let header = calendar.dequeueReusableJTAppleSupplementaryView(withReuseIdentifier: "DateHeader", for: indexPath) as! DateHeader 99 | header.monthTitle.text = formatter.string(from: range.start) 100 | return header 101 | } 102 | 103 | func calendarSizeForMonths(\_ calendar: JTAppleCalendarView?) -> MonthSize? { 104 | return MonthSize(defaultSize: 50) 105 | } 106 | ``` 107 | 108 | Now run your app. You should have a working calendar with an implemented header. 109 | 110 | ## Next Steps 111 | 112 | Find out more about [range selection style](../range-selection-styles/Range%20selection%20styles.md) 113 | -------------------------------------------------------------------------------- /docs/headers/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image1.png -------------------------------------------------------------------------------- /docs/headers/images/image2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image2.gif -------------------------------------------------------------------------------- /docs/headers/images/image3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image3.gif -------------------------------------------------------------------------------- /docs/headers/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image4.png -------------------------------------------------------------------------------- /docs/headers/images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image5.png -------------------------------------------------------------------------------- /docs/headers/images/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image6.png -------------------------------------------------------------------------------- /docs/headers/images/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image7.png -------------------------------------------------------------------------------- /docs/headers/images/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/headers/images/image8.png -------------------------------------------------------------------------------- /docs/implementing-week-numbers/Implementing week numbers.md: -------------------------------------------------------------------------------- 1 | # Implementing week numbers 2 | 3 | > [!WARNING] 4 | > This documentation requires assistance, particularly updating to SwiftUI. If possible, please submit a PR to help improve the documentation 5 | 6 | This library does not come with week numbers built in. However building one is easy. 7 | 8 | ## Design the views 9 | 10 | First we need to design the week number views. Head to storyboard and modify it to be similar to the image below. 11 | 12 | ![storyboard](./images/image1.png) 13 | 14 | A UILabel for the week number was added on top. A UICollectionView added below to hold the week numbers. Finally a UILabel was centered inside the UICollectionViewCell. Add proper constraints so that it appears visually as shown. 15 | 16 | ## Setup week-number cells 17 | 18 | Create a week number cell class to display the numbers. 19 | 20 | ```swift 21 | import UIKit 22 | 23 | class WeekCountCell: UICollectionViewCell { 24 | @IBOutlet var countLabel: UILabel! 25 | } 26 | ``` 27 | 28 | Now that we have our cell class, head to storyboard and set the class of the cell inside the UICollectionView to be `WeekCountCell` 29 | 30 | ![set cell class to WeekCountCell](./images/image2.png) 31 | 32 | Now that the class is set, connect the label IBOutlet to `countLabel` 33 | 34 | ## Setup the UICollectionView 35 | 36 | With the cell setup complete, let’s configure the collectionView. Create an IBOutlet for it in the ViewController subclass. 37 | 38 | > [!WARNING] 39 | > The old documentation includes the following section verbatim, but the code may be incomplete. Please use caution when following, and if it is incorrect, submit an issue or PR to improve this documentation 40 | 41 | ```swift 42 | class ViewController: UIViewController { 43 | @IBOutlet var calendarView: JTAppleCalendarView! 44 | @IBOutlet var weekCount: UICollectionView! 45 | 46 | override func viewDidLoad() { 47 | ``` 48 | 49 | Connect the UICollectionView’s reference outlet to `weekCount`. Also set its `dataSource` and delegate to be ViewController as seen below. 50 | 51 | ![set UICollectionView to weekCount](./images/image3.png) 52 | 53 | ## Final Setup code 54 | 55 | Head to the ViewController class and add the following code. We will assume that a year does not have more than 55 weeks. 56 | 57 | ```swift 58 | extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate { 59 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 60 | return 55 61 | } 62 | 63 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 64 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "WeekCountCell", for: indexPath) as! WeekCountCell 65 | cell.countLabel.text = "\(indexPath.item + 1)" 66 | return cell 67 | } 68 | } 69 | ``` 70 | 71 | Finally, we need this code to be run every time the user swipes the calendar. Therefore we will put in the willScroll delegate function. It will look better visually if placed there than if it were placed in the didScroll delegate function. Place the following code after the willDisplay function as shown below. 72 | 73 | > [!WARNING] 74 | > The old documentation includes the following section verbatim, but the code may be incomplete. Please use caution when following, and if it is incorrect, submit an issue or PR to improve this documentation 75 | 76 | ```swift 77 | func calendar(_ calendar: JTAppleCalendarView, willDisplay cell: JTAppleCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) { 78 | configureCell(view: cell, cellState: cellState) 79 | } 80 | 81 | func calendar(_ calendar: JTAppleCalendarView, willScrollToDateSegmentWith visibleDates: DateSegmentInfo) { 82 | let date: Date = visibleDates.monthDates.first!.date 83 | let weekNumber = Calendar.current.component(.weekOfYear, from: date) 84 | weekCount.scrollToItem(at: IndexPath(item: weekNumber - 1, section: 0), at: .top, animated: true) 85 | } 86 | 87 | func calendar(_ calendar: JTAppleCalendarView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) ->; JTAppleCollectionReusableView { 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/implementing-week-numbers/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/implementing-week-numbers/images/image1.png -------------------------------------------------------------------------------- /docs/implementing-week-numbers/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/implementing-week-numbers/images/image2.png -------------------------------------------------------------------------------- /docs/implementing-week-numbers/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/implementing-week-numbers/images/image3.png -------------------------------------------------------------------------------- /docs/implementing-week-numbers/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/implementing-week-numbers/images/image4.png -------------------------------------------------------------------------------- /docs/installation/Installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | This library can be installed in 3 ways; Cocoapods, Carthage, or manually. 4 | 5 | ## Via Cocoapods 6 | 7 | CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command: 8 | 9 | ``` 10 | gem install cocoapods 11 | ``` 12 | 13 | > [!NOTE] 14 | > CocoaPods 1.1.0+ is required to build JTApplecalendar. 15 | 16 | To integrate JTAppleCalendar into your Xcode project using CocoaPods, specify it in your Podfile: 17 | 18 | ``` 19 | source 'https://github.com/CocoaPods/Specs.git' 20 | platform :ios, '10.0' 21 | use_frameworks! 22 | 23 | target '' do 24 | pod 'JTAppleCalendar', '~>; 7.1' 25 | end 26 | ``` 27 | 28 | Then, run the following command: 29 | 30 | ``` 31 | pod install 32 | ``` 33 | 34 | Once installation is complete, JTAppleCalendar should be installed 35 | 36 | ## Troubleshooting 37 | 38 | If you’re new to CocoaPods, search how to integrate Cocoapods into your project. CocoaPods is one of the top dependency managers for integrating 3rd party frameworks into your project. But in a nut-shell, here is how to complete installation with a sample project called `test` 39 | 40 | 1. Install Cocoapods 41 | 2. Create a new xcode project. Save the name as: `test` 42 | 3. Go to your console in the directory location where your project is located 43 | 4. Type and run the command: `pod init` 44 | 45 | This will create a `Podfile` in that same location 46 | 47 | Edit `Podfile` so that it looks like the following: 48 | 49 | ``` 50 | # Uncomment the next line to define a global platform for your project 51 | 52 | # platform :ios, '9.0' 53 | 54 | target 'test' do 55 | use_frameworks! 56 | pod 'JTAppleCalendar' 57 | end 58 | ``` 59 | 60 | Save, and head back to terminal and run: `pod install` 61 | 62 | If all went well, installation should be complete. Close the XCodeproject, and instead reopen it using the workspace file which was generated when installation was completed 63 | 64 | ## Via Carthage 65 | 66 | Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 67 | 68 | You can install Carthage with Homebrew using the following commands: 69 | 70 | ``` 71 | brew update 72 | brew install carthage 73 | ``` 74 | 75 | To integrate JTAppleCalendar into your Xcode project using Carthage, specify it in your `Cartfile` 76 | 77 | ``` 78 | github "patchthecode/JTAppleCalendar" ~> 7.1 79 | ``` 80 | 81 | Run `carthage update` to build the framework and drag the built `JTApplecalendar.framework` into your Xcode project. 82 | 83 | ## Manually install 84 | 85 | Simply drag the source files into your project. Make sure you remove the unnecessary import statements where needed. 86 | 87 | ## Next steps 88 | 89 | Once installed, learn how to [build a calendar from scratch](../build-calendar/Build%20A%20Calendar%20From%20Scratch.md) 90 | -------------------------------------------------------------------------------- /docs/migration-guide/v8 Migration Guide.md: -------------------------------------------------------------------------------- 1 | # JTAppleCalendar Version 8.0.0 Migration guide 2 | 3 | Changes in version 8.0.0 are mainly that the function names are changed. why? We now support YearView. because of this, distiction has to be made between yearView and monthView. 4 | 5 | **Important**: Because the names of some function have changed, if you forget to change their names you might notice things like “my functions are not being called”. If you are experiencing this, changes are you did not change the names. 6 | 7 | ## Renamed Delegate Function Names 8 | 9 | ```swift 10 | func calendar(_ calendar: JTACMonthView, shouldSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool 11 | func calendar(_ calendar: JTACMonthView, shouldDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) -> Bool 12 | func calendar(_ calendar: JTACMonthView, didSelectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 13 | func calendar(_ calendar: JTACMonthView, didDeselectDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 14 | func calendar(_ calendar: JTACMonthView, didHighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 15 | func calendar(_ calendar: JTACMonthView, didUnhighlightDate date: Date, cell: JTACDayCell?, cellState: CellState, indexPath: IndexPath) 16 | func calendar(_ calendar: JTACMonthView, willScrollToDateSegmentWith visibleDates: DateSegmentInfo) 17 | func calendar(_ calendar: JTACMonthView, didScrollToDateSegmentWith visibleDates: DateSegmentInfo) 18 | func calendar(_ calendar: JTACMonthView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTACMonthReusableView 19 | func calendarDidScroll(_ calendar: JTACMonthView) 20 | func calendarSizeForMonths(_ calendar: JTACMonthView?) -> MonthSize? 21 | func sizeOfDecorationView(indexPath: IndexPath) -> CGRect 22 | func scrollDidEndDecelerating(for calendar: JTACMonthView) 23 | ``` 24 | 25 | ## New Object Names 26 | 27 | ```swift 28 | JTApplecalendarView --> JTACMonthView 29 | JTAppleCalendarViewDelegate --> JTACMonthViewDelegate 30 | JTAppleCalendarViewDataSource --> JTACMonthViewDataSource 31 | JTAppleDayCell --> JTACMonthCell 32 | JTAppleCollectionReusableView --> JTACMonthReusableView 33 | ``` 34 | 35 | ## REPORTING / NOTES 36 | 37 | If you used storyboards/xibs, remember to change the name of the classes in there as well. If we missed out any thing that caused the transition experience to be complicated, then open an issue and let us know right away. It will be fixed. 38 | -------------------------------------------------------------------------------- /docs/range-selection-styles/Range selection styles.md: -------------------------------------------------------------------------------- 1 | # Range selection styles 2 | 3 | > [!WARNING] 4 | > This documentation requires assistance, particularly updating to SwiftUI. If possible, please submit a PR to help improve the documentation 5 | 6 | ## Single tap range selection 7 | 8 | ![single tap range selection](./images/image1.gif) 9 | 10 | First let’s create an outlet for the calendarView. Add the following line and connect the IBOutlet to the calendarView on Storyboard. 11 | 12 | ```swift 13 | import UIKit 14 | import JTAppleCalendar 15 | 16 | class ViewController: UIViewController { 17 | @IBOutlet var calendarView: JTAppleCalendarView! 18 | } 19 | ``` 20 | 21 | ![connect IBOutlet](./images/image2.png) 22 | 23 | Now add the following code. 24 | 25 | ```swift 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | calendarView.allowsMultipleSelection = true 29 | calendarView.isRangeSelectionUsed = true 30 | } 31 | ``` 32 | 33 | Multiple selection is needed to multi-select. Informing that range-selection will be used makes the calendar refresh cells both to the left and right of a selected cell to update its selectedView. 34 | 35 | The final code needed for range selection is below. Modify the function to the following. 36 | 37 | ```swift 38 | func handleCellSelected(cell: DateCell, cellState: CellState) { 39 | cell.selectedView.isHidden = !cellState.isSelected 40 | switch cellState.selectedPosition() { 41 | case .left: 42 | cell.selectedView.layer.cornerRadius = 20 43 | cell.selectedView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMinXMinYCorner] 44 | case .middle: 45 | cell.selectedView.layer.cornerRadius = 0 46 | cell.selectedView.layer.maskedCorners = [] 47 | case .right: 48 | cell.selectedView.layer.cornerRadius = 20 49 | cell.selectedView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner] 50 | case .full: 51 | cell.selectedView.layer.cornerRadius = 20 52 | cell.selectedView.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner] 53 | default: break 54 | } 55 | } 56 | ``` 57 | 58 | The selected view was morphed in this example for simplicity, but you can customize your view to anything. 59 | 60 | This library determines a reasonable setting for what each cell’s selectedPosition should be. If your app needs something other than this default, then, you will have to implement it your self based on your own criteria. 61 | 62 | ## Drag to select range 63 | 64 | ![drag on screen to select range](./images/image3.gif) 65 | 66 | This example requires the `UITapGestureRecornizer` in order to work. We’ll start by modifying the following code: 67 | 68 | ```swift 69 | class ViewController: UIViewController { 70 | 71 | @IBOutlet var calendarView: JTAppleCalendarView! 72 | let testCalendar = Calendar(identifier: .gregorian) 73 | 74 | override func viewDidLoad() { 75 | super.viewDidLoad() 76 | 77 | calendarView.allowsMultipleSelection = true 78 | calendarView.isRangeSelectionUsed = true 79 | 80 | let panGensture = UILongPressGestureRecognizer(target: self, action: #selector(didStartRangeSelecting(gesture:))) 81 | panGensture.minimumPressDuration = 0.5 82 | calendarView.addGestureRecognizer(panGensture) 83 | } 84 | } 85 | ``` 86 | 87 | Note that we set the minimum press duration to 0.5 seconds. This means you need to press the cell for at least 0.5 seconds before drag-selection can begin. Also note the need for the `testCalendar` instance. We use this variable for date calculations in the function below. 88 | 89 | Finally, add the following function. 90 | 91 | ```swift 92 | @objc func didStartRangeSelecting(gesture: UILongPressGestureRecognizer) { 93 | let point = gesture.location(in: gesture.view!) 94 | let rangeSelectedDates = calendarView.selectedDates 95 | 96 | guard let cellState = calendarView.cellStatus(at: point) else { return } 97 | 98 | if !rangeSelectedDates.contains(cellState.date) { 99 | let dateRange = calendarView.generateDateRange(from: rangeSelectedDates.first ?? cellState.date, to: cellState.date) 100 | calendarView.selectDates(dateRange, keepSelectionIfMultiSelectionAllowed: true) 101 | } else { 102 | let followingDay = testCalendar.date(byAdding: .day, value: 1, to: cellState.date)! 103 | calendarView.selectDates(from: followingDay, to: rangeSelectedDates.last!, keepSelectionIfMultiSelectionAllowed: false) 104 | } 105 | } 106 | ``` 107 | 108 | The code above is just an example. If your drag-selection needs to behave differently, then please modify to suite your needs. 109 | 110 | ## Select range via multiple taps 111 | 112 | ![selct range via multiple taps](./images/image4.gif) 113 | 114 | > [!CAUTION] 115 | > This section is not available from the original docs source. If you can, please submit a PR to update this section 116 | 117 | ## Next Steps 118 | 119 | Learn more about [events and loading information from a server](../adding-events/Adding%20Events.md) 120 | -------------------------------------------------------------------------------- /docs/range-selection-styles/images/image1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/range-selection-styles/images/image1.gif -------------------------------------------------------------------------------- /docs/range-selection-styles/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/range-selection-styles/images/image2.png -------------------------------------------------------------------------------- /docs/range-selection-styles/images/image3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/range-selection-styles/images/image3.gif -------------------------------------------------------------------------------- /docs/range-selection-styles/images/image4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/range-selection-styles/images/image4.gif -------------------------------------------------------------------------------- /docs/scrolling-modes/Scrolling Modes.md: -------------------------------------------------------------------------------- 1 | # Scrolling modes 2 | 3 | > [!WARNING] 4 | > This section requires assistance. Please submit a PR if possible to help improve documentation 5 | 6 | There are 7 modes. 7 | 8 | calendarView.scrollingMode = 9 | 10 | 1. stopAtEachCalendarFrame 11 | 12 | Non-continuous scrolling. Calendar will stop scrolling when it has scrolled a distance of frame.width, before accepting another scroll. 13 | 14 | ![stop at each frame](./images/image1.gif) 15 | 16 | 2. stopAtEachSection 17 | 18 | Non-continuous scrolling. Calendar will stop scrolling when it has scrolled a distance of a section’s width, before accepting another scroll. Months can be divided into sections depending on how many rows per month you chose to display. 19 | 20 | ![stop at each section](./images/image2.gif) 21 | 22 | 3. stopAtEach(customInterval: CGFloat) 23 | 24 | Non-continuous scrolling. Calendar will stop scrolling at every point where your set interval has been reached. 25 | 26 | 4. nonStopToSection(withResistance: CGFloat) 27 | 28 | Continuous scrolling. Calendar will smoothly scroll and gradually decelerate. When stopped, it will snap to a section. Deceleration rate is based on resistance value (from 0.0 to 1.0) 29 | 30 | ![nonstop to section](./images/image3.gif) 31 | 32 | 5. nonStopToCell(withResistance: CGFloat) 33 | 34 | Continuous scrolling. Calendar will smoothly scroll and gradually decelerate. When stopped, it will snap to a cell. Deceleration rate is based on resistance value (from 0.0 to 1.0) 35 | 36 | ![nonstop to cell](./images/image4.gif) 37 | 38 | 6. nonStopTo(customInterval: CGFloat, withResistance: CGFloat) 39 | 40 | Continuous scrolling. Calendar will smoothly scroll and gradually decelerate. When stopped, it will snap to your set custom interval. Deceleration rate is based on resistance value (from 0.0 to 1.0) 41 | 42 | 7. none 43 | 44 | Continuous scrolling. Calendar will smoothly scroll and gradually decelerate till it stops. 45 | 46 | # Next Steps 47 | 48 | Learn how to [switch between month-view and week-view](../switch-month-to-week-view/Switch%20between%20month-view%20and%20week-view.md) 49 | -------------------------------------------------------------------------------- /docs/scrolling-modes/images/image1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/scrolling-modes/images/image1.gif -------------------------------------------------------------------------------- /docs/scrolling-modes/images/image2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/scrolling-modes/images/image2.gif -------------------------------------------------------------------------------- /docs/scrolling-modes/images/image3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/scrolling-modes/images/image3.gif -------------------------------------------------------------------------------- /docs/scrolling-modes/images/image4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/scrolling-modes/images/image4.gif -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/Switch between month-view and week-view.md: -------------------------------------------------------------------------------- 1 | # Switch between month-view and week-view 2 | 3 | > [!WARNING] 4 | > This documentation requires assistance, particularly updating to SwiftUI. If possible, please submit a PR to help improve the documentation 5 | 6 | This library has no concept of week/month view. You as the developer will need to define this. For this example, we will change state between a 6-row calendar and a 1-row calendar. 7 | 8 | Switching between a 6-row and 1-row calendar is pretty simple. First we’ll look at the configuration. Then we’ll look at the animation. 9 | 10 | ## Configuration 11 | 12 | ![indates and outdates](./images/image1.png) 13 | 14 | It is important to remember that if you switch your calendar from 6 rows to 1, the inDates/outDates will make your 1 row calendar look as if dates are being repeated. 15 | 16 | Although you can make your 1-row calendar with what ever design you want, the regular 6 row configuration will not work on a 1 row calendar for 90% of developers. This is because of the inDates/outDates shown above. 17 | 18 | Here is a proper configuration for a 1 row calendar 19 | 20 | ```swift 21 | let parameters = ConfigurationParameters( 22 | startDate: startDate, 23 | endDate: endDate, 24 | numberOfRows: 1, 25 | generateInDates: .forFirstMonthOnly, 26 | generateOutDates: .off, 27 | hasStrictBoundaries: false 28 | ) 29 | ``` 30 | 31 | Therefore, modify your `configureCalendar` function to have the following code 32 | 33 | ```swift 34 | func configureCalendar(_ calendar: JTAppleCalendarView) -> ConfigurationParameters { 35 | let formatter = DateFormatter() 36 | formatter.dateFormat = "yyyy MM dd" 37 | 38 | let startDate = formatter.date(from: "2018 01 01")! 39 | let endDate = Date() 40 | 41 | if numberOfRows == 6 { 42 | return ConfigurationParameters(startDate: startDate, endDate: endDate, numberOfRows: numberOfRows) 43 | } else { 44 | return ConfigurationParameters(startDate: startDate, 45 | endDate: endDate, 46 | numberOfRows: numberOfRows, 47 | generateInDates: .forFirstMonthOnly, 48 | generateOutDates: .off, 49 | hasStrictBoundaries: false) 50 | } 51 | } 52 | ``` 53 | 54 | ## Animation 55 | 56 | It all has to do with constraints. 57 | 58 | Go to storyboard and modify the height constraint of the calendar to 350. 59 | 60 | ![modify height](./images/image2.png) 61 | 62 | Next create a blue colored view at the bottom of the calendar. Make its left, right and bottom constraints fixed. But make it’s top constraint to sit right below the collectionView with zero (0) space. This view represents the view below the calendarView that you will build in your real app. See its completed constraints below. 63 | 64 | ![completed constraints](./images/image3.png) 65 | 66 | The views are now ready. What we now need is an outlet to the hight-constraint for the calendar. Click on the height constraint of the CollectionView and create an IBOutlet for it in your view controller class. 67 | 68 | ![create IBOutlet](./images/image4.png) 69 | 70 | The created IBOutlet should be called constraint in your ViewController subclass. Also add a new variable called numberOfRows. We will use this to change the row number. Below shows both added code. 71 | 72 | ```swift 73 | class ViewController: UIViewController { 74 | @IBOutlet var calendarView: JTAppleCalendarView! 75 | @IBOutlet weak var constraint: NSLayoutConstraint! 76 | 77 | var numberOfRows = 6 78 | } 79 | ``` 80 | 81 | Finally create a toggle button on storyboard 82 | 83 | ![create toggle](./images/image5.png) 84 | 85 | and connect its IBAction to the following code 86 | 87 | ```swift 88 | @IBAction func toggle(_ sender: Any) { 89 | if numberOfRows == 6 { 90 | self.constraint.constant = 58.33 91 | self.numberOfRows = 1 92 | UIView.animate(withDuration: 0.2, animations: { 93 | self.view.layoutIfNeeded() 94 | }) { completed in 95 | self.calendarView.reloadData(withanchor: Date()) 96 | } 97 | } else { 98 | self.constraint.constant = 350 99 | self.numberOfRows = 6 100 | 101 | UIView.animate(withDuration: 0.2, animations: { 102 | self.view.layoutIfNeeded() 103 | self.calendarView.reloadData(withanchor: Date()) 104 | }) 105 | } 106 | } 107 | ``` 108 | 109 | In the code above we set the `anchorDate` to be the current date, but you can set it to what ever date you wish. `anchorDate` is the date the calendar will focus on once the number of rows change. This parameter is optional. Remove if not needed. 110 | 111 | Finally delete the following 2 functions and run the app. These header functions are removed for simplicity. A new tutorial will be created on how headers affect 1 row calendars 112 | 113 | ```swift 114 | func calendar(_ calendar: JTAppleCalendarView, headerViewForDateRange range: (start: Date, end: Date), at indexPath: IndexPath) -> JTAppleCollectionReusableView {} 115 | func calendarSizeForMonths(_ calendar: JTAppleCalendarView?) -> MonthSize? {} 116 | ``` 117 | 118 | ## Next Steps 119 | 120 | Find out more about [headers](../headers/Headers.md) 121 | -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/switch-month-to-week-view/images/image1.png -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/switch-month-to-week-view/images/image2.png -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/switch-month-to-week-view/images/image3.png -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/switch-month-to-week-view/images/image4.png -------------------------------------------------------------------------------- /docs/switch-month-to-week-view/images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patchthecode/JTAppleCalendar/6a19c89f40b928c7bf7cf43a7df1fef739b8a0ab/docs/switch-month-to-week-view/images/image5.png --------------------------------------------------------------------------------