├── .gitignore ├── .swift-version ├── .travis.yml ├── Images ├── demo_gif.gif ├── icon.png ├── logo.png └── screenshots.png ├── LICENSE ├── README.md ├── YMCalendar.podspec ├── YMCalendar.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── YMCalendar.xcscheme ├── YMCalendar ├── Extension │ ├── ExtensionCompatible.swift │ ├── Foundation │ │ └── Calendar.swift │ └── UIKit │ │ ├── UICollectionReusableView.swift │ │ └── UICollectionView.swift ├── Info.plist ├── Month │ ├── Appearance │ │ └── YMCalendarAppearance.swift │ ├── Cell │ │ ├── EventView │ │ │ ├── ReusableObject.swift │ │ │ ├── ReusableObjectQueue.swift │ │ │ ├── YMEventStandardView.swift │ │ │ ├── YMEventView.swift │ │ │ └── YMEventsRowView.swift │ │ └── UICollectionReusableView │ │ │ ├── YMMonthBackgroundView.swift │ │ │ ├── YMMonthDayCollectionCell.swift │ │ │ └── YMMonthWeekView.swift │ ├── DataSource │ │ └── YMCalendarDataSource.swift │ ├── Delegate │ │ ├── YMCalendarDelegate.swift │ │ └── YMEventsRowViewDelegate.swift │ ├── Enum │ │ ├── YMDayLabelAlignment.swift │ │ ├── YMDecelerationRate.swift │ │ ├── YMScrollDirection.swift │ │ └── YMSelectAnimation.swift │ ├── EventKit │ │ └── EventKitManager.swift │ ├── Layout │ │ ├── YMCalendarLayout.swift │ │ └── YMCalendarLayoutDataSource.swift │ ├── ViewController │ │ ├── YMCalendarEKViewController.swift │ │ └── YMCalendarViewController.swift │ └── YMCalendarView.swift ├── Utils │ ├── DateRange.swift │ ├── IndexableDictionry │ │ ├── IndexableDictionary+Description.swift │ │ └── IndexableDictionary.swift │ ├── MonthDate.swift │ └── MonthRange.swift ├── WeekBar │ ├── YMCalendarWeekBarAppearance.swift │ └── YMCalendarWeekBarView.swift └── YMCalendar.h └── YMCalendarDemo ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-29@2x.png │ ├── Icon-29@3x.png │ ├── Icon-40@2x.png │ ├── Icon-40@3x.png │ ├── Icon-60@2x.png │ └── Icon-60@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── BasicViewController.swift ├── Color.swift ├── EKEventKitViewController.swift ├── GradientViewController.swift └── Info.plist /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | Build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | timeline.xctimeline 22 | 23 | # CocoaPods 24 | # 25 | # We recommend against adding the Pods directory to your .gitignore. However 26 | # you should judge for yourself, the pros and cons are mentioned at: 27 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 28 | # 29 | # Pods/ 30 | 31 | # Carthage 32 | # 33 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 34 | # Carthage/Checkouts 35 | 36 | Carthage/Build 37 | 38 | 39 | # Various 40 | 41 | .DS_Store 42 | 43 | 44 | # Linux 45 | 46 | *.swp 47 | *.swo 48 | 49 | # Swift Package Manager 50 | 51 | .build/ 52 | Packages/ 53 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.3 3 | xcode_workspace: YMCalendar.xcworkspace 4 | xcode_scheme: YMCalendar 5 | xcode_sdk: iphonesimulator 6 | notifications: 7 | email: false 8 | script: 9 | - set -o pipefail && xcodebuild build -workspace YMCalendar.xcworkspace -scheme YMCalendar|xcpretty 10 | 11 | -------------------------------------------------------------------------------- /Images/demo_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/Images/demo_gif.gif -------------------------------------------------------------------------------- /Images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/Images/icon.png -------------------------------------------------------------------------------- /Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/Images/logo.png -------------------------------------------------------------------------------- /Images/screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/Images/screenshots.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Yuma Matsune 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](Images/logo.png) 2 | --- 3 | 4 | [![Build Status](https://travis-ci.org/matsune/YMCalendar.svg?branch=master)](https://travis-ci.org/matsune/YMCalendar) 5 | [![Language](https://img.shields.io/badge/language-swift-orange.svg)](https://swift.org/) 6 | ![Platform](http://img.shields.io/badge/platform-ios-blue.svg?style=flat) 7 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) 8 | ![Cocoapods](https://img.shields.io/badge/pod-1.0-blue.svg) 9 | [![License](https://img.shields.io/badge/license-MIT-000000.svg)](https://github.com/matsune/YMCalendar/blob/master/LICENSE) 10 | 11 | # YMCalendar 12 | YMCalendar is a library of monthly event calendar for iOS written in Swift. 13 | 14 | ## Screenshots 15 | ![creen](Images/screenshots.png) 16 | 17 | ## GIF 18 |

19 | 20 |

21 | 22 | ## Usage 23 | - [Property](#Property) 24 | - [Layout](#Layout) 25 | - [Appearance](#Appearance) 26 | - [Gradient](#Gradient) 27 | - [Delegate](#Delegate) 28 | - [DataSource](#DataSource) 29 | - [EKEvent](#EKEvent) 30 | 31 | 32 | ### Property 33 | `YMCalendarView` has some instance properties like `UICollectionView`. 34 | - Scrollable both vertically and horizontally 35 | - Switching paging mode 36 | - Multiple selection mode 37 | 38 | ```swift 39 | var scrollDirection: YMScrollDirection 40 | var isPagingEnabled: Bool 41 | var allowsMultipleSelection: Bool 42 | ``` 43 | 44 | - Customizable select and deselect animation. 45 | 46 | ```swift 47 | enum YMSelectAnimation { 48 | case none, bounce, fade 49 | } 50 | 51 | var selectAnimation: YMSelectAnimation 52 | var deselectAnimation: YMSelectAnimation 53 | ``` 54 | 55 | - Customizable date range of calendarView. 56 | ```swift 57 | func setDateRange(_ dateRange: DateRange?) 58 | ``` 59 | If you set `nil` for dateRange, it will be inifinite scroll calendar (default is nil). 60 | 61 | ### Layout customization 62 | 63 | #### Appearance protocol 64 | YMCalendarView has `appearance` property of `YMCalendarAppearance` protocol which manages layout for `YMCalendarView`. 65 | For example, color and width of grid lines, color and fonts of day labels on calendarView. 66 | 67 | ```swift 68 | func horizontalGridColor(in view: YMCalendarView) -> UIColor 69 | func horizontalGridWidth(in view: YMCalendarView) -> CGFloat 70 | func verticalGridColor(in view: YMCalendarView) -> UIColor 71 | func verticalGridWidth(in view: YMCalendarView) -> CGFloat 72 | 73 | func dayLabelAlignment(in view: YMCalendarView) -> YMDayLabelAlignment 74 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelFontAtDate date: Date) -> UIFont 75 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor 76 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelBackgroundColorAtDate date: Date) -> UIColor 77 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor 78 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor 79 | ``` 80 | 81 | #### Gradient background 82 | You can set gradient colors for background of `YMCalendarView`. 83 | 84 | ```swift 85 | var gradientColors: [UIColor]? 86 | var gradientLocations: [NSNumber]? 87 | var gradientStartPoint: CGPoint 88 | var gradientEndPoint: CGPoint 89 | ``` 90 | 91 | ### Delegate 92 | `YMCalendarDelegate` protocol methods will be called by your scrolling and selection actions. 93 | The methods of this protocol are all optional. 94 | ```swift 95 | func calendarViewDidScroll(_ view: YMCalendarView) 96 | func calendarView(_ view: YMCalendarView, didSelectDayCellAtDate date: Date) 97 | func calendarView(_ view: YMCalendarView, didMoveMonthOfStartDate date: Date) 98 | func calendarView(_ view: YMCalendarView, shouldSelectEventAtIndex index: Int, date: Date) -> Bool 99 | func calendarView(_ view: YMCalendarView, didSelectEventAtIndex index: Int, date: Date) 100 | func calendarView(_ view: YMCalendarView, shouldDeselectEventAtIndex index: Int, date: Date) -> Bool 101 | func calendarView(_ view: YMCalendarView, didDeselectEventAtIndex index: Int, date: Date) 102 | ``` 103 | 104 | ### DataSource 105 | An object that abopts `YMCalendarDataSource` protocol is responsible for provising the data and views about events of days. 106 | ```swift 107 | func calendarView(_ view: YMCalendarView, numberOfEventsAtDate date: Date) -> Int 108 | func calendarView(_ view: YMCalendarView, dateRangeForEventAtIndex index: Int, date: Date) -> DateRange? 109 | func calendarView(_ view: YMCalendarView, eventViewForEventAtIndex index: Int, date: Date) -> YMEventView 110 | ``` 111 | 112 | If you want to create original eventView, it should inherit `YMEventView`. You can dequeue the original eventView by registering to calendarView with identifier (Please see demo project). 113 | 114 | ### EKEvent 115 | If you want to use EventKit as a data source, create an instance of `YMCalendarEKViewController`. This superclass has calendarView and system of loading EKEvents. Please see `EKEventKitViewController` in demo project. 116 | 117 | 118 | ## Installation 119 | ### Carthage 120 | ``` 121 | github "matsune/YMCalendar" 122 | ``` 123 | 124 | ### CocoaPods 125 | ``` 126 | pod "YMCalendar" 127 | ``` 128 | 129 | ## Author 130 | Yuma Matsune 131 | 132 | ## License 133 | YMCalendar is available under the MIT license. See the [LICENSE](https://github.com/matsune/YMCalendar/blob/master/LICENSE) file for more info. 134 | -------------------------------------------------------------------------------- /YMCalendar.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "YMCalendar" 3 | s.version = "1.0" 4 | s.summary = "Monthly event calendar framework in Swift" 5 | s.homepage = "https://github.com/matsune/YMCalendar" 6 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 7 | s.license = { :type => "MIT", :file => "LICENSE" } 8 | s.author = { "Yuma Matsune" => "yuma.matsune@gmail.com" } 9 | s.social_media_url = "https://twitter.com/matsune_ver3" 10 | s.source = { :git => "https://github.com/matsune/YMCalendar.git", :tag => s.version.to_s } 11 | s.platform = :ios, "8.0" 12 | 13 | s.source_files = "YMCalendar/**/*.swift" 14 | end 15 | -------------------------------------------------------------------------------- /YMCalendar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 13273C3621EAE47B00DE8D7E /* ReusableObjectQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13273C3421EAE47B00DE8D7E /* ReusableObjectQueue.swift */; }; 11 | 13273C3721EAE47B00DE8D7E /* ReusableObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13273C3521EAE47B00DE8D7E /* ReusableObject.swift */; }; 12 | 13273C3A21EAE48F00DE8D7E /* YMEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13273C3821EAE48F00DE8D7E /* YMEventView.swift */; }; 13 | 13273C3B21EAE48F00DE8D7E /* YMEventStandardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13273C3921EAE48F00DE8D7E /* YMEventStandardView.swift */; }; 14 | 1334000B1EB339DE007FE17E /* IndexableDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133400091EB339DE007FE17E /* IndexableDictionary.swift */; }; 15 | 1334000C1EB339DE007FE17E /* IndexableDictionary+Description.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1334000A1EB339DE007FE17E /* IndexableDictionary+Description.swift */; }; 16 | 1345C7161EBD87790076FF31 /* YMDayLabelAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1345C7151EBD87790076FF31 /* YMDayLabelAlignment.swift */; }; 17 | 135586BB20613ABB00897CE8 /* MonthRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586BA20613ABA00897CE8 /* MonthRange.swift */; }; 18 | 135586BD20613AD000897CE8 /* MonthDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586BC20613AD000897CE8 /* MonthDate.swift */; }; 19 | 135586C42062174900897CE8 /* ExtensionCompatible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586C32062174900897CE8 /* ExtensionCompatible.swift */; }; 20 | 135586C72062179A00897CE8 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586C62062179A00897CE8 /* UICollectionView.swift */; }; 21 | 135586C9206217F100897CE8 /* UICollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586C8206217F100897CE8 /* UICollectionReusableView.swift */; }; 22 | 135586CC20621C4E00897CE8 /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 135586CB20621C4E00897CE8 /* Calendar.swift */; }; 23 | 13A518B71E72E5A400F539ED /* YMCalendar.h in Headers */ = {isa = PBXBuildFile; fileRef = 13A518B51E72E5A400F539ED /* YMCalendar.h */; settings = {ATTRIBUTES = (Public, ); }; }; 24 | 13A519071E72EA3100F539ED /* DateRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F01E72EA3100F539ED /* DateRange.swift */; }; 25 | 13A519081E72EA3100F539ED /* YMDecelerationRate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F11E72EA3100F539ED /* YMDecelerationRate.swift */; }; 26 | 13A519091E72EA3100F539ED /* YMEventsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F21E72EA3100F539ED /* YMEventsRowView.swift */; }; 27 | 13A5190C1E72EA3100F539ED /* YMMonthBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F51E72EA3100F539ED /* YMMonthBackgroundView.swift */; }; 28 | 13A5190E1E72EA3100F539ED /* YMMonthDayCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F71E72EA3100F539ED /* YMMonthDayCollectionCell.swift */; }; 29 | 13A5190F1E72EA3100F539ED /* YMCalendarLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F81E72EA3100F539ED /* YMCalendarLayout.swift */; }; 30 | 13A519101E72EA3100F539ED /* YMCalendarLayoutDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518F91E72EA3100F539ED /* YMCalendarLayoutDataSource.swift */; }; 31 | 13A519121E72EA3100F539ED /* YMCalendarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518FB1E72EA3100F539ED /* YMCalendarView.swift */; }; 32 | 13A519131E72EA3100F539ED /* YMCalendarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518FC1E72EA3100F539ED /* YMCalendarAppearance.swift */; }; 33 | 13A519141E72EA3100F539ED /* YMCalendarDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518FD1E72EA3100F539ED /* YMCalendarDataSource.swift */; }; 34 | 13A519151E72EA3100F539ED /* YMCalendarDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518FE1E72EA3100F539ED /* YMCalendarDelegate.swift */; }; 35 | 13A519161E72EA3100F539ED /* YMMonthWeekView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A518FF1E72EA3100F539ED /* YMMonthWeekView.swift */; }; 36 | 13A519191E72EA3100F539ED /* YMScrollDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A519021E72EA3100F539ED /* YMScrollDirection.swift */; }; 37 | 13A519311E72F0B100F539ED /* YMEventsRowViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13A519301E72F0B100F539ED /* YMEventsRowViewDelegate.swift */; }; 38 | 13AAAAF41EBC1CFD002C8AB5 /* YMSelectAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13AAAAF31EBC1CFD002C8AB5 /* YMSelectAnimation.swift */; }; 39 | 13B222661E85F0FB00BFF183 /* YMCalendarWeekBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B222651E85F0FB00BFF183 /* YMCalendarWeekBarView.swift */; }; 40 | 13B2226A1E860E8C00BFF183 /* YMCalendarWeekBarAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13B222691E860E8C00BFF183 /* YMCalendarWeekBarAppearance.swift */; }; 41 | 13FCB3542146BCD4004ECB93 /* GradientViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCB3332146BC92004ECB93 /* GradientViewController.swift */; }; 42 | 13FCB3552146BCD4004ECB93 /* EKEventKitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCB3392146BC92004ECB93 /* EKEventKitViewController.swift */; }; 43 | 13FCB3562146BCD4004ECB93 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCB33A2146BC92004ECB93 /* AppDelegate.swift */; }; 44 | 13FCB3572146BCD4004ECB93 /* BasicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCB33B2146BC92004ECB93 /* BasicViewController.swift */; }; 45 | 13FCB3582146BCD4004ECB93 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCB33C2146BC92004ECB93 /* Color.swift */; }; 46 | 13FCB35A2146BD44004ECB93 /* YMCalendar.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 13A518B21E72E5A400F539ED /* YMCalendar.framework */; }; 47 | 13FCB35B2146BD7B004ECB93 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13FCB3372146BC92004ECB93 /* Main.storyboard */; }; 48 | 13FCB35C2146BD7E004ECB93 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 13FCB3352146BC92004ECB93 /* LaunchScreen.storyboard */; }; 49 | 13FCB35D2146BD82004ECB93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13FCB3342146BC92004ECB93 /* Assets.xcassets */; }; 50 | 13FCCEE21E90DD460070B038 /* YMCalendarEKViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCCEE11E90DD460070B038 /* YMCalendarEKViewController.swift */; }; 51 | 13FCCEE61E90DEB80070B038 /* EventKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCCEE51E90DEB80070B038 /* EventKitManager.swift */; }; 52 | 13FCCEEB1E9135B30070B038 /* YMCalendarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13FCCEEA1E9135B30070B038 /* YMCalendarViewController.swift */; }; 53 | /* End PBXBuildFile section */ 54 | 55 | /* Begin PBXFileReference section */ 56 | 13273C3421EAE47B00DE8D7E /* ReusableObjectQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableObjectQueue.swift; sourceTree = ""; }; 57 | 13273C3521EAE47B00DE8D7E /* ReusableObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReusableObject.swift; sourceTree = ""; }; 58 | 13273C3821EAE48F00DE8D7E /* YMEventView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMEventView.swift; sourceTree = ""; }; 59 | 13273C3921EAE48F00DE8D7E /* YMEventStandardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMEventStandardView.swift; sourceTree = ""; }; 60 | 133400091EB339DE007FE17E /* IndexableDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndexableDictionary.swift; sourceTree = ""; }; 61 | 1334000A1EB339DE007FE17E /* IndexableDictionary+Description.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "IndexableDictionary+Description.swift"; sourceTree = ""; }; 62 | 1345C7151EBD87790076FF31 /* YMDayLabelAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMDayLabelAlignment.swift; sourceTree = ""; }; 63 | 135586BA20613ABA00897CE8 /* MonthRange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthRange.swift; sourceTree = ""; }; 64 | 135586BC20613AD000897CE8 /* MonthDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MonthDate.swift; sourceTree = ""; }; 65 | 135586C32062174900897CE8 /* ExtensionCompatible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionCompatible.swift; sourceTree = ""; }; 66 | 135586C62062179A00897CE8 /* UICollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionView.swift; sourceTree = ""; }; 67 | 135586C8206217F100897CE8 /* UICollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionReusableView.swift; sourceTree = ""; }; 68 | 135586CB20621C4E00897CE8 /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; 69 | 13A518B21E72E5A400F539ED /* YMCalendar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YMCalendar.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 13A518B51E72E5A400F539ED /* YMCalendar.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YMCalendar.h; sourceTree = ""; }; 71 | 13A518B61E72E5A400F539ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | 13A518F01E72EA3100F539ED /* DateRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateRange.swift; sourceTree = ""; }; 73 | 13A518F11E72EA3100F539ED /* YMDecelerationRate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMDecelerationRate.swift; sourceTree = ""; }; 74 | 13A518F21E72EA3100F539ED /* YMEventsRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMEventsRowView.swift; sourceTree = ""; }; 75 | 13A518F51E72EA3100F539ED /* YMMonthBackgroundView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMMonthBackgroundView.swift; sourceTree = ""; }; 76 | 13A518F71E72EA3100F539ED /* YMMonthDayCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMMonthDayCollectionCell.swift; sourceTree = ""; }; 77 | 13A518F81E72EA3100F539ED /* YMCalendarLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarLayout.swift; sourceTree = ""; }; 78 | 13A518F91E72EA3100F539ED /* YMCalendarLayoutDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarLayoutDataSource.swift; sourceTree = ""; }; 79 | 13A518FB1E72EA3100F539ED /* YMCalendarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarView.swift; sourceTree = ""; }; 80 | 13A518FC1E72EA3100F539ED /* YMCalendarAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarAppearance.swift; sourceTree = ""; }; 81 | 13A518FD1E72EA3100F539ED /* YMCalendarDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarDataSource.swift; sourceTree = ""; }; 82 | 13A518FE1E72EA3100F539ED /* YMCalendarDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarDelegate.swift; sourceTree = ""; }; 83 | 13A518FF1E72EA3100F539ED /* YMMonthWeekView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMMonthWeekView.swift; sourceTree = ""; }; 84 | 13A519021E72EA3100F539ED /* YMScrollDirection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMScrollDirection.swift; sourceTree = ""; }; 85 | 13A519301E72F0B100F539ED /* YMEventsRowViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMEventsRowViewDelegate.swift; sourceTree = ""; }; 86 | 13AAAAF31EBC1CFD002C8AB5 /* YMSelectAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMSelectAnimation.swift; sourceTree = ""; }; 87 | 13B222651E85F0FB00BFF183 /* YMCalendarWeekBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarWeekBarView.swift; sourceTree = ""; }; 88 | 13B222691E860E8C00BFF183 /* YMCalendarWeekBarAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarWeekBarAppearance.swift; sourceTree = ""; }; 89 | 13FCB3332146BC92004ECB93 /* GradientViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientViewController.swift; sourceTree = ""; }; 90 | 13FCB3342146BC92004ECB93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 91 | 13FCB3362146BC92004ECB93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 92 | 13FCB3382146BC92004ECB93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 93 | 13FCB3392146BC92004ECB93 /* EKEventKitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EKEventKitViewController.swift; sourceTree = ""; }; 94 | 13FCB33A2146BC92004ECB93 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 95 | 13FCB33B2146BC92004ECB93 /* BasicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicViewController.swift; sourceTree = ""; }; 96 | 13FCB33C2146BC92004ECB93 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; 97 | 13FCB33D2146BC92004ECB93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98 | 13FCB3422146BCA9004ECB93 /* YMCalendarDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = YMCalendarDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | 13FCCEE11E90DD460070B038 /* YMCalendarEKViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarEKViewController.swift; sourceTree = ""; }; 100 | 13FCCEE51E90DEB80070B038 /* EventKitManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventKitManager.swift; sourceTree = ""; }; 101 | 13FCCEEA1E9135B30070B038 /* YMCalendarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YMCalendarViewController.swift; sourceTree = ""; }; 102 | /* End PBXFileReference section */ 103 | 104 | /* Begin PBXFrameworksBuildPhase section */ 105 | 13A518AE1E72E5A400F539ED /* Frameworks */ = { 106 | isa = PBXFrameworksBuildPhase; 107 | buildActionMask = 2147483647; 108 | files = ( 109 | ); 110 | runOnlyForDeploymentPostprocessing = 0; 111 | }; 112 | 13FCB33F2146BCA9004ECB93 /* Frameworks */ = { 113 | isa = PBXFrameworksBuildPhase; 114 | buildActionMask = 2147483647; 115 | files = ( 116 | 13FCB35A2146BD44004ECB93 /* YMCalendar.framework in Frameworks */, 117 | ); 118 | runOnlyForDeploymentPostprocessing = 0; 119 | }; 120 | /* End PBXFrameworksBuildPhase section */ 121 | 122 | /* Begin PBXGroup section */ 123 | 132629CC1EB9639300261332 /* Cell */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 13A5192E1E72EF6900F539ED /* EventView */, 127 | 13A5192F1E72EF9600F539ED /* UICollectionReusableView */, 128 | ); 129 | path = Cell; 130 | sourceTree = ""; 131 | }; 132 | 133400081EB339CF007FE17E /* IndexableDictionry */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 1334000A1EB339DE007FE17E /* IndexableDictionary+Description.swift */, 136 | 133400091EB339DE007FE17E /* IndexableDictionary.swift */, 137 | ); 138 | path = IndexableDictionry; 139 | sourceTree = ""; 140 | }; 141 | 1334000D1EB369B4007FE17E /* Month */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 13A518FB1E72EA3100F539ED /* YMCalendarView.swift */, 145 | 13A5192C1E72EF0C00F539ED /* Appearance */, 146 | 132629CC1EB9639300261332 /* Cell */, 147 | 13A5192B1E72EF0100F539ED /* DataSource */, 148 | 13A5192A1E72EEF000F539ED /* Delegate */, 149 | 13A519261E72EE8A00F539ED /* Enum */, 150 | 13B518E41EAB9C6E002749AF /* EventKit */, 151 | 13A519281E72EECE00F539ED /* Layout */, 152 | 13FCCEE91E9135A60070B038 /* ViewController */, 153 | ); 154 | path = Month; 155 | sourceTree = ""; 156 | }; 157 | 1334000E1EB369C1007FE17E /* WeekBar */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 13B222691E860E8C00BFF183 /* YMCalendarWeekBarAppearance.swift */, 161 | 13B222651E85F0FB00BFF183 /* YMCalendarWeekBarView.swift */, 162 | ); 163 | path = WeekBar; 164 | sourceTree = ""; 165 | }; 166 | 135586C22062173A00897CE8 /* Extension */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 135586CA20621C4200897CE8 /* Foundation */, 170 | 135586C52062178C00897CE8 /* UIKit */, 171 | 135586C32062174900897CE8 /* ExtensionCompatible.swift */, 172 | ); 173 | path = Extension; 174 | sourceTree = ""; 175 | }; 176 | 135586C52062178C00897CE8 /* UIKit */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 135586C62062179A00897CE8 /* UICollectionView.swift */, 180 | 135586C8206217F100897CE8 /* UICollectionReusableView.swift */, 181 | ); 182 | path = UIKit; 183 | sourceTree = ""; 184 | }; 185 | 135586CA20621C4200897CE8 /* Foundation */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 135586CB20621C4E00897CE8 /* Calendar.swift */, 189 | ); 190 | path = Foundation; 191 | sourceTree = ""; 192 | }; 193 | 13A518A81E72E5A400F539ED = { 194 | isa = PBXGroup; 195 | children = ( 196 | 13A518B31E72E5A400F539ED /* Products */, 197 | 13A518B41E72E5A400F539ED /* YMCalendar */, 198 | 13FCB3322146BC92004ECB93 /* YMCalendarDemo */, 199 | 13FCB3592146BD44004ECB93 /* Frameworks */, 200 | ); 201 | sourceTree = ""; 202 | }; 203 | 13A518B31E72E5A400F539ED /* Products */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | 13A518B21E72E5A400F539ED /* YMCalendar.framework */, 207 | 13FCB3422146BCA9004ECB93 /* YMCalendarDemo.app */, 208 | ); 209 | name = Products; 210 | sourceTree = ""; 211 | }; 212 | 13A518B41E72E5A400F539ED /* YMCalendar */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 135586C22062173A00897CE8 /* Extension */, 216 | 1334000D1EB369B4007FE17E /* Month */, 217 | 13A519251E72EE7A00F539ED /* Utils */, 218 | 1334000E1EB369C1007FE17E /* WeekBar */, 219 | 13A518B61E72E5A400F539ED /* Info.plist */, 220 | 13A518B51E72E5A400F539ED /* YMCalendar.h */, 221 | ); 222 | path = YMCalendar; 223 | sourceTree = ""; 224 | }; 225 | 13A519251E72EE7A00F539ED /* Utils */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | 135586BA20613ABA00897CE8 /* MonthRange.swift */, 229 | 135586BC20613AD000897CE8 /* MonthDate.swift */, 230 | 13A518F01E72EA3100F539ED /* DateRange.swift */, 231 | 133400081EB339CF007FE17E /* IndexableDictionry */, 232 | ); 233 | path = Utils; 234 | sourceTree = ""; 235 | }; 236 | 13A519261E72EE8A00F539ED /* Enum */ = { 237 | isa = PBXGroup; 238 | children = ( 239 | 1345C7151EBD87790076FF31 /* YMDayLabelAlignment.swift */, 240 | 13A518F11E72EA3100F539ED /* YMDecelerationRate.swift */, 241 | 13A519021E72EA3100F539ED /* YMScrollDirection.swift */, 242 | 13AAAAF31EBC1CFD002C8AB5 /* YMSelectAnimation.swift */, 243 | ); 244 | path = Enum; 245 | sourceTree = ""; 246 | }; 247 | 13A519281E72EECE00F539ED /* Layout */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | 13A518F81E72EA3100F539ED /* YMCalendarLayout.swift */, 251 | 13A518F91E72EA3100F539ED /* YMCalendarLayoutDataSource.swift */, 252 | ); 253 | path = Layout; 254 | sourceTree = ""; 255 | }; 256 | 13A5192A1E72EEF000F539ED /* Delegate */ = { 257 | isa = PBXGroup; 258 | children = ( 259 | 13A518FE1E72EA3100F539ED /* YMCalendarDelegate.swift */, 260 | 13A519301E72F0B100F539ED /* YMEventsRowViewDelegate.swift */, 261 | ); 262 | path = Delegate; 263 | sourceTree = ""; 264 | }; 265 | 13A5192B1E72EF0100F539ED /* DataSource */ = { 266 | isa = PBXGroup; 267 | children = ( 268 | 13A518FD1E72EA3100F539ED /* YMCalendarDataSource.swift */, 269 | ); 270 | path = DataSource; 271 | sourceTree = ""; 272 | }; 273 | 13A5192C1E72EF0C00F539ED /* Appearance */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | 13A518FC1E72EA3100F539ED /* YMCalendarAppearance.swift */, 277 | ); 278 | path = Appearance; 279 | sourceTree = ""; 280 | }; 281 | 13A5192E1E72EF6900F539ED /* EventView */ = { 282 | isa = PBXGroup; 283 | children = ( 284 | 13273C3521EAE47B00DE8D7E /* ReusableObject.swift */, 285 | 13273C3421EAE47B00DE8D7E /* ReusableObjectQueue.swift */, 286 | 13A518F21E72EA3100F539ED /* YMEventsRowView.swift */, 287 | 13273C3921EAE48F00DE8D7E /* YMEventStandardView.swift */, 288 | 13273C3821EAE48F00DE8D7E /* YMEventView.swift */, 289 | ); 290 | path = EventView; 291 | sourceTree = ""; 292 | }; 293 | 13A5192F1E72EF9600F539ED /* UICollectionReusableView */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | 13A518F51E72EA3100F539ED /* YMMonthBackgroundView.swift */, 297 | 13A518F71E72EA3100F539ED /* YMMonthDayCollectionCell.swift */, 298 | 13A518FF1E72EA3100F539ED /* YMMonthWeekView.swift */, 299 | ); 300 | path = UICollectionReusableView; 301 | sourceTree = ""; 302 | }; 303 | 13B518E41EAB9C6E002749AF /* EventKit */ = { 304 | isa = PBXGroup; 305 | children = ( 306 | 13FCCEE51E90DEB80070B038 /* EventKitManager.swift */, 307 | ); 308 | path = EventKit; 309 | sourceTree = ""; 310 | }; 311 | 13FCB3322146BC92004ECB93 /* YMCalendarDemo */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | 13FCB3332146BC92004ECB93 /* GradientViewController.swift */, 315 | 13FCB3342146BC92004ECB93 /* Assets.xcassets */, 316 | 13FCB3352146BC92004ECB93 /* LaunchScreen.storyboard */, 317 | 13FCB3372146BC92004ECB93 /* Main.storyboard */, 318 | 13FCB3392146BC92004ECB93 /* EKEventKitViewController.swift */, 319 | 13FCB33A2146BC92004ECB93 /* AppDelegate.swift */, 320 | 13FCB33B2146BC92004ECB93 /* BasicViewController.swift */, 321 | 13FCB33C2146BC92004ECB93 /* Color.swift */, 322 | 13FCB33D2146BC92004ECB93 /* Info.plist */, 323 | ); 324 | path = YMCalendarDemo; 325 | sourceTree = ""; 326 | }; 327 | 13FCB3592146BD44004ECB93 /* Frameworks */ = { 328 | isa = PBXGroup; 329 | children = ( 330 | ); 331 | name = Frameworks; 332 | sourceTree = ""; 333 | }; 334 | 13FCCEE91E9135A60070B038 /* ViewController */ = { 335 | isa = PBXGroup; 336 | children = ( 337 | 13FCCEE11E90DD460070B038 /* YMCalendarEKViewController.swift */, 338 | 13FCCEEA1E9135B30070B038 /* YMCalendarViewController.swift */, 339 | ); 340 | path = ViewController; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXGroup section */ 344 | 345 | /* Begin PBXHeadersBuildPhase section */ 346 | 13A518AF1E72E5A400F539ED /* Headers */ = { 347 | isa = PBXHeadersBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | 13A518B71E72E5A400F539ED /* YMCalendar.h in Headers */, 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | /* End PBXHeadersBuildPhase section */ 355 | 356 | /* Begin PBXNativeTarget section */ 357 | 13A518B11E72E5A400F539ED /* YMCalendar */ = { 358 | isa = PBXNativeTarget; 359 | buildConfigurationList = 13A518BA1E72E5A400F539ED /* Build configuration list for PBXNativeTarget "YMCalendar" */; 360 | buildPhases = ( 361 | 13A518AD1E72E5A400F539ED /* Sources */, 362 | 13A518AE1E72E5A400F539ED /* Frameworks */, 363 | 13A518AF1E72E5A400F539ED /* Headers */, 364 | 13A518B01E72E5A400F539ED /* Resources */, 365 | ); 366 | buildRules = ( 367 | ); 368 | dependencies = ( 369 | ); 370 | name = YMCalendar; 371 | productName = YMCalendar; 372 | productReference = 13A518B21E72E5A400F539ED /* YMCalendar.framework */; 373 | productType = "com.apple.product-type.framework"; 374 | }; 375 | 13FCB3412146BCA9004ECB93 /* YMCalendarDemo */ = { 376 | isa = PBXNativeTarget; 377 | buildConfigurationList = 13FCB3512146BCAB004ECB93 /* Build configuration list for PBXNativeTarget "YMCalendarDemo" */; 378 | buildPhases = ( 379 | 13FCB33E2146BCA9004ECB93 /* Sources */, 380 | 13FCB33F2146BCA9004ECB93 /* Frameworks */, 381 | 13FCB3402146BCA9004ECB93 /* Resources */, 382 | ); 383 | buildRules = ( 384 | ); 385 | dependencies = ( 386 | ); 387 | name = YMCalendarDemo; 388 | productName = YMCalendarDemo; 389 | productReference = 13FCB3422146BCA9004ECB93 /* YMCalendarDemo.app */; 390 | productType = "com.apple.product-type.application"; 391 | }; 392 | /* End PBXNativeTarget section */ 393 | 394 | /* Begin PBXProject section */ 395 | 13A518A91E72E5A400F539ED /* Project object */ = { 396 | isa = PBXProject; 397 | attributes = { 398 | LastSwiftUpdateCheck = 0940; 399 | LastUpgradeCheck = 0940; 400 | ORGANIZATIONNAME = "Yuma Matsune"; 401 | TargetAttributes = { 402 | 13A518B11E72E5A400F539ED = { 403 | CreatedOnToolsVersion = 8.2.1; 404 | LastSwiftMigration = 1010; 405 | ProvisioningStyle = Automatic; 406 | }; 407 | 13FCB3412146BCA9004ECB93 = { 408 | CreatedOnToolsVersion = 9.4.1; 409 | DevelopmentTeam = 8G7ZB52773; 410 | LastSwiftMigration = 1010; 411 | ProvisioningStyle = Automatic; 412 | }; 413 | }; 414 | }; 415 | buildConfigurationList = 13A518AC1E72E5A400F539ED /* Build configuration list for PBXProject "YMCalendar" */; 416 | compatibilityVersion = "Xcode 3.2"; 417 | developmentRegion = English; 418 | hasScannedForEncodings = 0; 419 | knownRegions = ( 420 | en, 421 | Base, 422 | ); 423 | mainGroup = 13A518A81E72E5A400F539ED; 424 | productRefGroup = 13A518B31E72E5A400F539ED /* Products */; 425 | projectDirPath = ""; 426 | projectRoot = ""; 427 | targets = ( 428 | 13A518B11E72E5A400F539ED /* YMCalendar */, 429 | 13FCB3412146BCA9004ECB93 /* YMCalendarDemo */, 430 | ); 431 | }; 432 | /* End PBXProject section */ 433 | 434 | /* Begin PBXResourcesBuildPhase section */ 435 | 13A518B01E72E5A400F539ED /* Resources */ = { 436 | isa = PBXResourcesBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | ); 440 | runOnlyForDeploymentPostprocessing = 0; 441 | }; 442 | 13FCB3402146BCA9004ECB93 /* Resources */ = { 443 | isa = PBXResourcesBuildPhase; 444 | buildActionMask = 2147483647; 445 | files = ( 446 | 13FCB35B2146BD7B004ECB93 /* Main.storyboard in Resources */, 447 | 13FCB35D2146BD82004ECB93 /* Assets.xcassets in Resources */, 448 | 13FCB35C2146BD7E004ECB93 /* LaunchScreen.storyboard in Resources */, 449 | ); 450 | runOnlyForDeploymentPostprocessing = 0; 451 | }; 452 | /* End PBXResourcesBuildPhase section */ 453 | 454 | /* Begin PBXSourcesBuildPhase section */ 455 | 13A518AD1E72E5A400F539ED /* Sources */ = { 456 | isa = PBXSourcesBuildPhase; 457 | buildActionMask = 2147483647; 458 | files = ( 459 | 13FCCEE21E90DD460070B038 /* YMCalendarEKViewController.swift in Sources */, 460 | 13273C3721EAE47B00DE8D7E /* ReusableObject.swift in Sources */, 461 | 135586C42062174900897CE8 /* ExtensionCompatible.swift in Sources */, 462 | 13A519101E72EA3100F539ED /* YMCalendarLayoutDataSource.swift in Sources */, 463 | 135586CC20621C4E00897CE8 /* Calendar.swift in Sources */, 464 | 13A519121E72EA3100F539ED /* YMCalendarView.swift in Sources */, 465 | 13273C3621EAE47B00DE8D7E /* ReusableObjectQueue.swift in Sources */, 466 | 13AAAAF41EBC1CFD002C8AB5 /* YMSelectAnimation.swift in Sources */, 467 | 135586BB20613ABB00897CE8 /* MonthRange.swift in Sources */, 468 | 13A519151E72EA3100F539ED /* YMCalendarDelegate.swift in Sources */, 469 | 13A519091E72EA3100F539ED /* YMEventsRowView.swift in Sources */, 470 | 13B2226A1E860E8C00BFF183 /* YMCalendarWeekBarAppearance.swift in Sources */, 471 | 13A5190C1E72EA3100F539ED /* YMMonthBackgroundView.swift in Sources */, 472 | 135586BD20613AD000897CE8 /* MonthDate.swift in Sources */, 473 | 13A519131E72EA3100F539ED /* YMCalendarAppearance.swift in Sources */, 474 | 13FCCEE61E90DEB80070B038 /* EventKitManager.swift in Sources */, 475 | 13A519191E72EA3100F539ED /* YMScrollDirection.swift in Sources */, 476 | 13B222661E85F0FB00BFF183 /* YMCalendarWeekBarView.swift in Sources */, 477 | 13A5190F1E72EA3100F539ED /* YMCalendarLayout.swift in Sources */, 478 | 13273C3A21EAE48F00DE8D7E /* YMEventView.swift in Sources */, 479 | 135586C9206217F100897CE8 /* UICollectionReusableView.swift in Sources */, 480 | 13A519141E72EA3100F539ED /* YMCalendarDataSource.swift in Sources */, 481 | 13A519071E72EA3100F539ED /* DateRange.swift in Sources */, 482 | 13A519311E72F0B100F539ED /* YMEventsRowViewDelegate.swift in Sources */, 483 | 1334000C1EB339DE007FE17E /* IndexableDictionary+Description.swift in Sources */, 484 | 13A5190E1E72EA3100F539ED /* YMMonthDayCollectionCell.swift in Sources */, 485 | 135586C72062179A00897CE8 /* UICollectionView.swift in Sources */, 486 | 13273C3B21EAE48F00DE8D7E /* YMEventStandardView.swift in Sources */, 487 | 1334000B1EB339DE007FE17E /* IndexableDictionary.swift in Sources */, 488 | 1345C7161EBD87790076FF31 /* YMDayLabelAlignment.swift in Sources */, 489 | 13FCCEEB1E9135B30070B038 /* YMCalendarViewController.swift in Sources */, 490 | 13A519161E72EA3100F539ED /* YMMonthWeekView.swift in Sources */, 491 | 13A519081E72EA3100F539ED /* YMDecelerationRate.swift in Sources */, 492 | ); 493 | runOnlyForDeploymentPostprocessing = 0; 494 | }; 495 | 13FCB33E2146BCA9004ECB93 /* Sources */ = { 496 | isa = PBXSourcesBuildPhase; 497 | buildActionMask = 2147483647; 498 | files = ( 499 | 13FCB3562146BCD4004ECB93 /* AppDelegate.swift in Sources */, 500 | 13FCB3572146BCD4004ECB93 /* BasicViewController.swift in Sources */, 501 | 13FCB3552146BCD4004ECB93 /* EKEventKitViewController.swift in Sources */, 502 | 13FCB3582146BCD4004ECB93 /* Color.swift in Sources */, 503 | 13FCB3542146BCD4004ECB93 /* GradientViewController.swift in Sources */, 504 | ); 505 | runOnlyForDeploymentPostprocessing = 0; 506 | }; 507 | /* End PBXSourcesBuildPhase section */ 508 | 509 | /* Begin PBXVariantGroup section */ 510 | 13FCB3352146BC92004ECB93 /* LaunchScreen.storyboard */ = { 511 | isa = PBXVariantGroup; 512 | children = ( 513 | 13FCB3362146BC92004ECB93 /* Base */, 514 | ); 515 | name = LaunchScreen.storyboard; 516 | sourceTree = ""; 517 | }; 518 | 13FCB3372146BC92004ECB93 /* Main.storyboard */ = { 519 | isa = PBXVariantGroup; 520 | children = ( 521 | 13FCB3382146BC92004ECB93 /* Base */, 522 | ); 523 | name = Main.storyboard; 524 | sourceTree = ""; 525 | }; 526 | /* End PBXVariantGroup section */ 527 | 528 | /* Begin XCBuildConfiguration section */ 529 | 13A518B81E72E5A400F539ED /* Debug */ = { 530 | isa = XCBuildConfiguration; 531 | buildSettings = { 532 | ALWAYS_SEARCH_USER_PATHS = NO; 533 | CLANG_ANALYZER_NONNULL = YES; 534 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 535 | CLANG_CXX_LIBRARY = "libc++"; 536 | CLANG_ENABLE_MODULES = YES; 537 | CLANG_ENABLE_OBJC_ARC = YES; 538 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 539 | CLANG_WARN_BOOL_CONVERSION = YES; 540 | CLANG_WARN_COMMA = YES; 541 | CLANG_WARN_CONSTANT_CONVERSION = YES; 542 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 543 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 544 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 545 | CLANG_WARN_EMPTY_BODY = YES; 546 | CLANG_WARN_ENUM_CONVERSION = YES; 547 | CLANG_WARN_INFINITE_RECURSION = YES; 548 | CLANG_WARN_INT_CONVERSION = YES; 549 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 550 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 551 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 552 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 553 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 554 | CLANG_WARN_STRICT_PROTOTYPES = YES; 555 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 556 | CLANG_WARN_UNREACHABLE_CODE = YES; 557 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 558 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 559 | COPY_PHASE_STRIP = NO; 560 | CURRENT_PROJECT_VERSION = 1; 561 | DEBUG_INFORMATION_FORMAT = dwarf; 562 | ENABLE_STRICT_OBJC_MSGSEND = YES; 563 | ENABLE_TESTABILITY = YES; 564 | GCC_C_LANGUAGE_STANDARD = gnu99; 565 | GCC_DYNAMIC_NO_PIC = NO; 566 | GCC_NO_COMMON_BLOCKS = YES; 567 | GCC_OPTIMIZATION_LEVEL = 0; 568 | GCC_PREPROCESSOR_DEFINITIONS = ( 569 | "DEBUG=1", 570 | "$(inherited)", 571 | ); 572 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 573 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 574 | GCC_WARN_UNDECLARED_SELECTOR = YES; 575 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 576 | GCC_WARN_UNUSED_FUNCTION = YES; 577 | GCC_WARN_UNUSED_VARIABLE = YES; 578 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 579 | MTL_ENABLE_DEBUG_INFO = YES; 580 | ONLY_ACTIVE_ARCH = YES; 581 | SDKROOT = iphoneos; 582 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 583 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 584 | TARGETED_DEVICE_FAMILY = "1,2"; 585 | VERSIONING_SYSTEM = "apple-generic"; 586 | VERSION_INFO_PREFIX = ""; 587 | }; 588 | name = Debug; 589 | }; 590 | 13A518B91E72E5A400F539ED /* Release */ = { 591 | isa = XCBuildConfiguration; 592 | buildSettings = { 593 | ALWAYS_SEARCH_USER_PATHS = NO; 594 | CLANG_ANALYZER_NONNULL = YES; 595 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 596 | CLANG_CXX_LIBRARY = "libc++"; 597 | CLANG_ENABLE_MODULES = YES; 598 | CLANG_ENABLE_OBJC_ARC = YES; 599 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 600 | CLANG_WARN_BOOL_CONVERSION = YES; 601 | CLANG_WARN_COMMA = YES; 602 | CLANG_WARN_CONSTANT_CONVERSION = YES; 603 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 604 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 605 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 606 | CLANG_WARN_EMPTY_BODY = YES; 607 | CLANG_WARN_ENUM_CONVERSION = YES; 608 | CLANG_WARN_INFINITE_RECURSION = YES; 609 | CLANG_WARN_INT_CONVERSION = YES; 610 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 611 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 612 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 613 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 614 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 615 | CLANG_WARN_STRICT_PROTOTYPES = YES; 616 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 617 | CLANG_WARN_UNREACHABLE_CODE = YES; 618 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 619 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 620 | COPY_PHASE_STRIP = NO; 621 | CURRENT_PROJECT_VERSION = 1; 622 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 623 | ENABLE_NS_ASSERTIONS = NO; 624 | ENABLE_STRICT_OBJC_MSGSEND = YES; 625 | GCC_C_LANGUAGE_STANDARD = gnu99; 626 | GCC_NO_COMMON_BLOCKS = YES; 627 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 628 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 629 | GCC_WARN_UNDECLARED_SELECTOR = YES; 630 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 631 | GCC_WARN_UNUSED_FUNCTION = YES; 632 | GCC_WARN_UNUSED_VARIABLE = YES; 633 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 634 | MTL_ENABLE_DEBUG_INFO = NO; 635 | SDKROOT = iphoneos; 636 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 637 | TARGETED_DEVICE_FAMILY = "1,2"; 638 | VALIDATE_PRODUCT = YES; 639 | VERSIONING_SYSTEM = "apple-generic"; 640 | VERSION_INFO_PREFIX = ""; 641 | }; 642 | name = Release; 643 | }; 644 | 13A518BB1E72E5A400F539ED /* Debug */ = { 645 | isa = XCBuildConfiguration; 646 | buildSettings = { 647 | CLANG_ENABLE_MODULES = YES; 648 | CODE_SIGN_IDENTITY = "iPhone Developer"; 649 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 650 | DEFINES_MODULE = YES; 651 | DEVELOPMENT_TEAM = ""; 652 | DYLIB_COMPATIBILITY_VERSION = 1; 653 | DYLIB_CURRENT_VERSION = 1; 654 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 655 | INFOPLIST_FILE = YMCalendar/Info.plist; 656 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 657 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 658 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 659 | PRODUCT_BUNDLE_IDENTIFIER = co.matsune.YMCalendar; 660 | PRODUCT_NAME = "$(TARGET_NAME)"; 661 | SKIP_INSTALL = YES; 662 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 663 | SWIFT_VERSION = 4.2; 664 | }; 665 | name = Debug; 666 | }; 667 | 13A518BC1E72E5A400F539ED /* Release */ = { 668 | isa = XCBuildConfiguration; 669 | buildSettings = { 670 | CLANG_ENABLE_MODULES = YES; 671 | CODE_SIGN_IDENTITY = "iPhone Developer"; 672 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 673 | DEFINES_MODULE = YES; 674 | DEVELOPMENT_TEAM = ""; 675 | DYLIB_COMPATIBILITY_VERSION = 1; 676 | DYLIB_CURRENT_VERSION = 1; 677 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 678 | INFOPLIST_FILE = YMCalendar/Info.plist; 679 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 680 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 681 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 682 | PRODUCT_BUNDLE_IDENTIFIER = co.matsune.YMCalendar; 683 | PRODUCT_NAME = "$(TARGET_NAME)"; 684 | SKIP_INSTALL = YES; 685 | SWIFT_VERSION = 4.2; 686 | }; 687 | name = Release; 688 | }; 689 | 13FCB3522146BCAB004ECB93 /* Debug */ = { 690 | isa = XCBuildConfiguration; 691 | buildSettings = { 692 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 693 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 694 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 695 | CLANG_ENABLE_OBJC_WEAK = YES; 696 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 697 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 698 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 699 | CODE_SIGN_IDENTITY = "iPhone Developer"; 700 | CODE_SIGN_STYLE = Automatic; 701 | DEVELOPMENT_TEAM = 8G7ZB52773; 702 | GCC_C_LANGUAGE_STANDARD = gnu11; 703 | INFOPLIST_FILE = YMCalendarDemo/Info.plist; 704 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 705 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 706 | PRODUCT_BUNDLE_IDENTIFIER = matsune.YMCalendarDemo; 707 | PRODUCT_NAME = "$(TARGET_NAME)"; 708 | SWIFT_VERSION = 4.2; 709 | TARGETED_DEVICE_FAMILY = "1,2"; 710 | }; 711 | name = Debug; 712 | }; 713 | 13FCB3532146BCAB004ECB93 /* Release */ = { 714 | isa = XCBuildConfiguration; 715 | buildSettings = { 716 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 717 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 718 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 719 | CLANG_ENABLE_OBJC_WEAK = YES; 720 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 721 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 722 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 723 | CODE_SIGN_IDENTITY = "iPhone Developer"; 724 | CODE_SIGN_STYLE = Automatic; 725 | DEVELOPMENT_TEAM = 8G7ZB52773; 726 | GCC_C_LANGUAGE_STANDARD = gnu11; 727 | INFOPLIST_FILE = YMCalendarDemo/Info.plist; 728 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 729 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 730 | PRODUCT_BUNDLE_IDENTIFIER = matsune.YMCalendarDemo; 731 | PRODUCT_NAME = "$(TARGET_NAME)"; 732 | SWIFT_VERSION = 4.2; 733 | TARGETED_DEVICE_FAMILY = "1,2"; 734 | }; 735 | name = Release; 736 | }; 737 | /* End XCBuildConfiguration section */ 738 | 739 | /* Begin XCConfigurationList section */ 740 | 13A518AC1E72E5A400F539ED /* Build configuration list for PBXProject "YMCalendar" */ = { 741 | isa = XCConfigurationList; 742 | buildConfigurations = ( 743 | 13A518B81E72E5A400F539ED /* Debug */, 744 | 13A518B91E72E5A400F539ED /* Release */, 745 | ); 746 | defaultConfigurationIsVisible = 0; 747 | defaultConfigurationName = Release; 748 | }; 749 | 13A518BA1E72E5A400F539ED /* Build configuration list for PBXNativeTarget "YMCalendar" */ = { 750 | isa = XCConfigurationList; 751 | buildConfigurations = ( 752 | 13A518BB1E72E5A400F539ED /* Debug */, 753 | 13A518BC1E72E5A400F539ED /* Release */, 754 | ); 755 | defaultConfigurationIsVisible = 0; 756 | defaultConfigurationName = Release; 757 | }; 758 | 13FCB3512146BCAB004ECB93 /* Build configuration list for PBXNativeTarget "YMCalendarDemo" */ = { 759 | isa = XCConfigurationList; 760 | buildConfigurations = ( 761 | 13FCB3522146BCAB004ECB93 /* Debug */, 762 | 13FCB3532146BCAB004ECB93 /* Release */, 763 | ); 764 | defaultConfigurationIsVisible = 0; 765 | defaultConfigurationName = Release; 766 | }; 767 | /* End XCConfigurationList section */ 768 | }; 769 | rootObject = 13A518A91E72E5A400F539ED /* Project object */; 770 | } 771 | -------------------------------------------------------------------------------- /YMCalendar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /YMCalendar.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /YMCalendar.xcodeproj/xcshareddata/xcschemes/YMCalendar.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /YMCalendar/Extension/ExtensionCompatible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtensionCompatible.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/21. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Extension { 12 | let base: Base 13 | init (_ base: Base) { 14 | self.base = base 15 | } 16 | } 17 | 18 | protocol ExtensionCompatible { 19 | associatedtype Compatible 20 | static var ym: Extension.Type { get } 21 | var ym: Extension { get } 22 | } 23 | 24 | extension ExtensionCompatible { 25 | static var ym: Extension.Type { 26 | return Extension.self 27 | } 28 | 29 | var ym: Extension { 30 | return Extension(self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /YMCalendar/Extension/Foundation/Calendar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calendar.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/21. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Calendar { 12 | func day(_ date: Date) -> Int { 13 | guard let day = dateComponents([.day], from: date).day else { 14 | fatalError() 15 | } 16 | return day 17 | } 18 | 19 | public func endOfDayForDate(_ date: Date) -> Date { 20 | var comps = dateComponents([.year, .month, .day], from: self.date(byAdding: .day, value: 1, to: date)!) 21 | comps.second = -1 22 | return self.date(from: comps)! 23 | } 24 | 25 | public func startOfMonthForDate(_ date: Date) -> Date { 26 | var comp = dateComponents([.year, .month, .day], from: date) 27 | comp.day = 1 28 | return self.date(from: comp)! 29 | } 30 | 31 | public func endOfMonthForDate(_ date: Date) -> Date { 32 | var comp = dateComponents([.year, .month, .day], from: date) 33 | if let month = comp.month { 34 | comp.month = month + 1 35 | } 36 | comp.day = 0 37 | return self.date(from: comp)! 38 | } 39 | 40 | func nextStartOfMonthForDate(_ date: Date) -> Date { 41 | let firstDay = startOfMonthForDate(date) 42 | var comp = DateComponents() 43 | comp.month = 1 44 | return self.date(byAdding: comp, to: firstDay)! 45 | } 46 | 47 | func numberOfDaysInMonth(date: Date) -> Int { 48 | return range(of: .day, in: .month, for: date)?.count ?? 0 49 | } 50 | 51 | func numberOfWeeksInMonth(date: Date) -> Int { 52 | return range(of: .weekOfMonth, in: .month, for: date)?.count ?? 0 53 | } 54 | 55 | func date(from monthDate: MonthDate) -> Date { 56 | let dateComponents = DateComponents(year: monthDate.year, month: monthDate.month, day: 1) 57 | guard let date = date(from: dateComponents) else { 58 | fatalError() 59 | } 60 | return date 61 | } 62 | 63 | func monthDate(from date: Date) -> MonthDate { 64 | let comp = dateComponents([.year, .month], from: date) 65 | guard let year = comp.year, let month = comp.month else { 66 | fatalError() 67 | } 68 | return MonthDate(year: year, month: month) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /YMCalendar/Extension/UIKit/UICollectionReusableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionReusableView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/21. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UICollectionReusableView: ExtensionCompatible {} 13 | 14 | extension Extension where Base: UICollectionReusableView { 15 | static var kind: String { 16 | return "\(type(of: self))Kind" 17 | } 18 | 19 | static var identifier: String { 20 | return "\(type(of: self))Identifier" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /YMCalendar/Extension/UIKit/UICollectionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/21. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UICollectionView: ExtensionCompatible {} 13 | 14 | extension Extension where Base: UICollectionView { 15 | func register(_ type: T.Type) { 16 | if type is UICollectionViewCell.Type { 17 | base.register(type, forCellWithReuseIdentifier: type.ym.identifier) 18 | } else { 19 | base.register(type, forSupplementaryViewOfKind: type.ym.kind, withReuseIdentifier: type.ym.identifier) 20 | } 21 | } 22 | 23 | func dequeue(_ type: T.Type, for indexPath: IndexPath) -> T { 24 | if type is UICollectionViewCell.Type { 25 | guard let cell = base.dequeueReusableCell(withReuseIdentifier: type.ym.identifier, for: indexPath) as? T else { 26 | fatalError("Failed to dequeue type \(type)") 27 | } 28 | return cell 29 | } else { 30 | guard let view = base.dequeueReusableSupplementaryView(ofKind: type.ym.kind, withReuseIdentifier: type.ym.identifier, for: indexPath) as? T else { 31 | fatalError("Failed to dequeue type \(type)") 32 | } 33 | return view 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /YMCalendar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /YMCalendar/Month/Appearance/YMCalendarAppearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarAppearance.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/06. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public protocol YMCalendarAppearance: class { 13 | func horizontalGridColor(in view: YMCalendarView) -> UIColor 14 | func horizontalGridWidth(in view: YMCalendarView) -> CGFloat 15 | func verticalGridColor(in view: YMCalendarView) -> UIColor 16 | func verticalGridWidth(in view: YMCalendarView) -> CGFloat 17 | 18 | func dayLabelAlignment(in view: YMCalendarView) -> YMDayLabelAlignment 19 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelFontAtDate date: Date) -> UIFont 20 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor 21 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelBackgroundColorAtDate date: Date) -> UIColor 22 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor 23 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor 24 | } 25 | 26 | extension YMCalendarAppearance { 27 | public func horizontalGridColor(in view: YMCalendarView) -> UIColor { 28 | return .black 29 | } 30 | 31 | public func horizontalGridWidth(in view: YMCalendarView) -> CGFloat { 32 | return 0.3 33 | } 34 | 35 | public func verticalGridColor(in view: YMCalendarView) -> UIColor { 36 | return .black 37 | } 38 | 39 | public func verticalGridWidth(in view: YMCalendarView) -> CGFloat { 40 | return 0.3 41 | } 42 | 43 | public func dayLabelAlignment(in view: YMCalendarView) -> YMDayLabelAlignment { 44 | return .left 45 | } 46 | 47 | public func calendarViewAppearance(_ view: YMCalendarView, dayLabelFontAtDate date: Date) -> UIFont { 48 | return .systemFont(ofSize: 10.0) 49 | } 50 | 51 | public func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor { 52 | return .black 53 | } 54 | 55 | public func calendarViewAppearance(_ view: YMCalendarView, dayLabelBackgroundColorAtDate date: Date) -> UIColor { 56 | return .clear 57 | } 58 | 59 | public func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor { 60 | return .white 61 | } 62 | 63 | public func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor { 64 | return .black 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/EventView/ReusableObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableObject.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/07. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// ReusableObject will be reused for many times, such as UICollectionViewCell. 12 | /// This object can be registared and dequeued. 13 | public protocol ReusableObject: class { 14 | 15 | init() 16 | 17 | var reuseIdentifier: String { get set } 18 | 19 | func prepareForReuse() 20 | } 21 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/EventView/ReusableObjectQueue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableObjectQueue.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/04/28. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final internal class ReusableObjectQueue { 12 | typealias T = ReusableObject 13 | 14 | var reusableObjects: [String: T] = [:] 15 | 16 | var objectClasses: [String: T.Type] = [:] 17 | 18 | var totalCreated = 0 19 | 20 | var count: Int { 21 | return reusableObjects.count 22 | } 23 | 24 | func registerClass(_ objectClass: T.Type?, forObjectWithReuseIdentifier identifier: String) { 25 | if let objClass = objectClass { 26 | objectClasses[identifier] = objClass 27 | } else { 28 | objectClasses.removeValue(forKey: identifier) 29 | reusableObjects.removeValue(forKey: identifier) 30 | } 31 | } 32 | 33 | func enqueueReusableObject(_ object: T) { 34 | reusableObjects[object.reuseIdentifier] = object 35 | } 36 | 37 | func dequeueReusableObjectWithIdentifier(_ identifier: String) -> T? { 38 | if let object = reusableObjects[identifier] { 39 | reusableObjects.removeValue(forKey: identifier) 40 | object.prepareForReuse() 41 | return object 42 | } else { 43 | guard let anyClass = objectClasses[identifier] else { 44 | fatalError("\(identifier) is not registered.") 45 | } 46 | let object = anyClass.init() 47 | totalCreated += 1 48 | object.reuseIdentifier = identifier 49 | return object 50 | } 51 | } 52 | 53 | func removeAll() { 54 | reusableObjects.removeAll() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/EventView/YMEventStandardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMEventStandardView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/09. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class YMEventStandardView: YMEventView { 13 | 14 | private let kSpace: CGFloat = 2 15 | 16 | public var title: String = "" 17 | 18 | public var textColor: UIColor = .white 19 | 20 | public var font: UIFont = .systemFont(ofSize: 12.0) 21 | 22 | public var attrString = NSMutableAttributedString() 23 | 24 | public var baselineOffset: Float = 0.0 25 | 26 | override public func layoutSubviews() { 27 | super.layoutSubviews() 28 | setNeedsDisplay() 29 | } 30 | 31 | override public func prepareForReuse() { 32 | super.prepareForReuse() 33 | setNeedsDisplay() 34 | } 35 | 36 | private func redrawStringInRect(_ rect: CGRect) { 37 | let style = NSMutableParagraphStyle() 38 | style.lineBreakMode = .byClipping 39 | 40 | let attributedString = NSMutableAttributedString(string: title, 41 | attributes: [ 42 | NSAttributedString.Key.font: font, 43 | NSAttributedString.Key.paragraphStyle: style, 44 | NSAttributedString.Key.foregroundColor: textColor, 45 | NSAttributedString.Key.baselineOffset: baselineOffset]) 46 | 47 | attrString = attributedString 48 | } 49 | 50 | override public func draw(_ rect: CGRect) { 51 | let drawRect = rect.insetBy(dx: kSpace, dy: 0) 52 | redrawStringInRect(drawRect) 53 | 54 | attrString.draw(with: drawRect, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin], context: nil) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/EventView/YMEventView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMEventView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/06. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | open class YMEventView: UIView, ReusableObject { 13 | 14 | public var reuseIdentifier: String = "" 15 | 16 | public var selected: Bool = false 17 | 18 | public var visibleHeight: CGFloat = 0 19 | 20 | override public init(frame: CGRect) { 21 | super.init(frame: frame) 22 | commonInit() 23 | } 24 | 25 | required public init?(coder aDecoder: NSCoder) { 26 | super.init(coder: aDecoder) 27 | commonInit() 28 | } 29 | 30 | private func commonInit() { 31 | clipsToBounds = true 32 | } 33 | 34 | public func prepareForReuse() { 35 | selected = false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/EventView/YMEventsRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMEventsRowView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/06. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class YMEventsRowView: UIScrollView { 13 | 14 | weak var eventsRowDelegate: YMEventsRowViewDelegate? 15 | 16 | var monthStart: Date! 17 | 18 | var daysRange = NSRange() 19 | 20 | var dayWidth: CGFloat = 100 21 | 22 | var itemHeight: CGFloat = 18 23 | 24 | let cellSpacing: CGFloat = 2.0 25 | 26 | var eventViews: [IndexPath: YMEventView] = [:] 27 | 28 | var maxVisibleLines: Int? 29 | 30 | override init(frame: CGRect) { 31 | super.init(frame: frame) 32 | commonInit() 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | super.init(coder: aDecoder) 37 | commonInit() 38 | } 39 | 40 | private func commonInit() { 41 | contentSize = CGSize(width: frame.width, height: 400) 42 | clipsToBounds = true 43 | backgroundColor = .clear 44 | showsVerticalScrollIndicator = false 45 | showsHorizontalScrollIndicator = false 46 | } 47 | 48 | private func removeAllEventViews() { 49 | eventViews.forEach { $0.value.removeFromSuperview() } 50 | eventViews.removeAll() 51 | } 52 | 53 | func reload() { 54 | removeAllEventViews() 55 | 56 | var lines = [IndexSet]() 57 | 58 | for day in daysRange.location.. [YMEventView] { 111 | return eventViews.filter { $0.value.frame.intersects(rect) }.map { $0.value } 112 | } 113 | 114 | func indexPathForCellAtPoint(_ point: CGPoint) -> IndexPath? { 115 | for (indexPath, cell) in eventViews { 116 | if cell.frame.contains(point) { 117 | return indexPath 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | func eventView(at indexPath: IndexPath) -> YMEventView? { 124 | return eventViews[indexPath] 125 | } 126 | 127 | private func rectForCell(range: NSRange, line: Int) -> CGRect { 128 | let colStart = range.location - daysRange.location 129 | let x = dayWidth * CGFloat(colStart) 130 | let y = CGFloat(line) * (itemHeight + cellSpacing) 131 | let w = dayWidth * CGFloat(range.length) 132 | let rect = CGRect(x: x, y: y, width: w, height: itemHeight) 133 | return rect.insetBy(dx: cellSpacing, dy: 0) 134 | } 135 | 136 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 137 | let hitView = super.hitTest(point, with: event) 138 | return hitView == self ? nil : hitView 139 | } 140 | 141 | @objc 142 | func handleTap(_ gesture: UITapGestureRecognizer) { 143 | if let indexPath = eventViews.first(where: { $0.value == gesture.view })?.key { 144 | eventsRowDelegate?.eventsRowView(self, didSelectCellAtIndexPath: indexPath) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/UICollectionReusableView/YMMonthBackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMMonthBackgroundView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/22. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class YMMonthBackgroundView: UICollectionReusableView { 13 | 14 | // number of days in week 15 | let numberOfColumns: Int = 7 16 | 17 | // number of week in month 18 | var numberOfRows: Int = 0 19 | 20 | // which column the last day of month is 21 | var lastColumn: Int = 7 22 | 23 | var horizontalGridWidth: CGFloat = 0.3 24 | var horizontalGridColor: UIColor = .black 25 | var verticalGridWidth: CGFloat = 0.3 26 | var verticalGridColor: UIColor = .black 27 | 28 | override init(frame: CGRect) { 29 | super.init(frame: frame) 30 | 31 | backgroundColor = .clear 32 | isUserInteractionEnabled = false 33 | } 34 | 35 | required init?(coder aDecoder: NSCoder) { 36 | fatalError("init(coder:) has not been implemented") 37 | } 38 | 39 | override func layoutSubviews() { 40 | super.layoutSubviews() 41 | setNeedsDisplay() 42 | } 43 | 44 | override func draw(_ rect: CGRect) { 45 | let c = UIGraphicsGetCurrentContext() 46 | 47 | let colWidth = numberOfColumns > 0 ? (bounds.width / CGFloat(numberOfColumns)) : bounds.width 48 | let rowHeight = numberOfRows > 0 ? (bounds.height / CGFloat(numberOfRows)) : bounds.height 49 | 50 | var x1: CGFloat 51 | var y1: CGFloat 52 | var x2: CGFloat 53 | var y2: CGFloat 54 | 55 | c?.setStrokeColor(horizontalGridColor.cgColor) 56 | c?.setLineWidth(horizontalGridWidth) 57 | c?.beginPath() 58 | 59 | if horizontalGridWidth > 0 { 60 | var i: Int = 0 61 | while i <= numberOfRows && numberOfRows != 0 { 62 | y1 = rowHeight * CGFloat(i) 63 | y2 = y1 64 | x1 = 0 65 | x2 = rect.maxX 66 | 67 | c?.move(to: CGPoint(x: x1, y: y1)) 68 | c?.addLine(to: CGPoint(x: x2, y: y2)) 69 | 70 | i += 1 71 | } 72 | } 73 | 74 | c?.strokePath() 75 | 76 | c?.setStrokeColor(verticalGridColor.cgColor) 77 | c?.setLineWidth(verticalGridWidth) 78 | c?.beginPath() 79 | 80 | if verticalGridWidth > 0 { 81 | for j in 0...numberOfColumns { 82 | x1 = colWidth * CGFloat(j) 83 | x2 = x1 84 | y1 = 0 85 | y2 = rect.maxY 86 | 87 | c?.move(to: CGPoint(x: x1, y: y1)) 88 | c?.addLine(to: CGPoint(x: x2, y: y2)) 89 | } 90 | } 91 | 92 | c?.strokePath() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/UICollectionReusableView/YMMonthDayCollectionCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMMonthDayCollectionCell.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/21. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class YMMonthDayCollectionCell: UICollectionViewCell { 13 | typealias YMMonthDayAnimationCompletion = (Bool) -> Void 14 | 15 | let dayLabel = UILabel() 16 | 17 | var dayLabelColor: UIColor = .black { 18 | didSet { 19 | dayLabel.textColor = dayLabelColor 20 | } 21 | } 22 | 23 | var dayLabelBackgroundColor: UIColor = .clear { 24 | didSet { 25 | dayLabel.backgroundColor = dayLabelBackgroundColor 26 | } 27 | } 28 | 29 | var dayLabelSelectedColor: UIColor = .white 30 | 31 | var dayLabelSelectedBackgroundColor: UIColor = .black 32 | 33 | var day: Int = 1 { 34 | didSet { 35 | dayLabel.text = "\(day)" 36 | } 37 | } 38 | 39 | var dayLabelAlignment: YMDayLabelAlignment = .left 40 | 41 | var dayLabelHeight: CGFloat = 15 42 | 43 | let dayLabelMargin: CGFloat = 2.0 44 | 45 | override init(frame: CGRect) { 46 | super.init(frame: frame) 47 | setup() 48 | } 49 | 50 | required public init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | 54 | private func setup() { 55 | backgroundColor = .clear 56 | 57 | dayLabel.numberOfLines = 1 58 | dayLabel.adjustsFontSizeToFitWidth = true 59 | dayLabel.font = UIFont.systemFont(ofSize: 12.0) 60 | dayLabel.textColor = dayLabelColor 61 | dayLabel.layer.masksToBounds = true 62 | dayLabel.textAlignment = .center 63 | contentView.addSubview(dayLabel) 64 | } 65 | 66 | override func prepareForReuse() { 67 | super.prepareForReuse() 68 | deselect(withAnimation: .none) 69 | } 70 | 71 | override public func layoutSubviews() { 72 | super.layoutSubviews() 73 | 74 | let x: CGFloat 75 | switch dayLabelAlignment { 76 | case .left: 77 | x = dayLabelMargin 78 | dayLabel.frame = CGRect(x: dayLabelMargin, y: dayLabelMargin, width: dayLabelHeight, height: dayLabelHeight) 79 | case .center: 80 | x = (bounds.width - dayLabelHeight)/2 81 | case .right: 82 | x = bounds.width - dayLabelMargin - dayLabelHeight 83 | } 84 | dayLabel.frame = CGRect(x: x, y: dayLabelMargin, width: dayLabelHeight, height: dayLabelHeight) 85 | dayLabel.layer.cornerRadius = dayLabelHeight / 2 86 | } 87 | 88 | public func select(withAnimation animation: YMSelectAnimation, completion: YMMonthDayAnimationCompletion? = nil) { 89 | switch animation { 90 | case .none: 91 | animationWithNone(true, completion: completion) 92 | case .bounce: 93 | animationWithBounce(true, completion: completion) 94 | case .fade: 95 | animationWithFade(true, completion: completion) 96 | } 97 | } 98 | 99 | public func deselect(withAnimation animation: YMSelectAnimation, completion: YMMonthDayAnimationCompletion? = nil) { 100 | switch animation { 101 | case .none: 102 | animationWithNone(false, completion: completion) 103 | case .bounce: 104 | animationWithBounce(false, completion: completion) 105 | case .fade: 106 | animationWithFade(false, completion: completion) 107 | } 108 | } 109 | 110 | // - MARK: Animation None 111 | private func animationWithNone(_ isSelected: Bool, completion: YMMonthDayAnimationCompletion?=nil) { 112 | if isSelected { 113 | dayLabel.textColor = dayLabelSelectedColor 114 | dayLabel.backgroundColor = dayLabelSelectedBackgroundColor 115 | } else { 116 | dayLabel.textColor = dayLabelColor 117 | dayLabel.backgroundColor = dayLabelBackgroundColor 118 | } 119 | completion?(true) 120 | } 121 | 122 | // - MARK: Animation Bounce 123 | private func animationWithBounce(_ isSelected: Bool, completion: YMMonthDayAnimationCompletion?) { 124 | dayLabel.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) 125 | 126 | UIView.animate(withDuration: 0.5, 127 | delay: 0, 128 | usingSpringWithDamping: 0.3, 129 | initialSpringVelocity: 0.1, 130 | options: .beginFromCurrentState, 131 | animations: { 132 | self.dayLabel.transform = CGAffineTransform(scaleX: 1, y: 1) 133 | 134 | self.animationWithNone(isSelected) 135 | }, completion: completion) 136 | } 137 | 138 | // - MARK: Animation Fade 139 | private func animationWithFade(_ isSelected: Bool, completion: YMMonthDayAnimationCompletion?) { 140 | UIView.transition(with: dayLabel, 141 | duration: 0.2, 142 | options: .transitionCrossDissolve, 143 | animations: { 144 | self.animationWithNone(isSelected) 145 | }, completion: completion) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /YMCalendar/Month/Cell/UICollectionReusableView/YMMonthWeekView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMMonthWeekView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/22. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class YMMonthWeekView: UICollectionReusableView { 13 | 14 | var eventsView = YMEventsRowView(frame: .zero) { 15 | didSet { 16 | oldValue.removeFromSuperview() 17 | addSubview(eventsView) 18 | } 19 | } 20 | 21 | override init(frame: CGRect) { 22 | super.init(frame: frame) 23 | commonInit() 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | commonInit() 29 | } 30 | 31 | private func commonInit() { 32 | backgroundColor = .clear 33 | addSubview(eventsView) 34 | } 35 | 36 | override func layoutSubviews() { 37 | super.layoutSubviews() 38 | eventsView.frame = bounds 39 | } 40 | 41 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 42 | let hitView = super.hitTest(point, with: event) 43 | return hitView == self ? nil : hitView 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /YMCalendar/Month/DataSource/YMCalendarDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarDataSource.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/09. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol YMCalendarDataSource: class { 12 | func calendarView(_ view: YMCalendarView, numberOfEventsAtDate date: Date) -> Int 13 | func calendarView(_ view: YMCalendarView, dateRangeForEventAtIndex index: Int, date: Date) -> DateRange? 14 | func calendarView(_ view: YMCalendarView, eventViewForEventAtIndex index: Int, date: Date) -> YMEventView 15 | } 16 | -------------------------------------------------------------------------------- /YMCalendar/Month/Delegate/YMCalendarDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarDelegate.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/23. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import EventKit 12 | 13 | @objc 14 | public protocol YMCalendarDelegate: class { 15 | @objc optional func calendarViewDidScroll(_ view: YMCalendarView) 16 | @objc optional func calendarView(_ view: YMCalendarView, didSelectDayCellAtDate date: Date) 17 | @objc optional func calendarView(_ view: YMCalendarView, didMoveMonthOfStartDate date: Date) 18 | @objc optional func calendarView(_ view: YMCalendarView, didSelectEventAtIndex index: Int, date: Date) 19 | } 20 | -------------------------------------------------------------------------------- /YMCalendar/Month/Delegate/YMEventsRowViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMEventsRowViewDelegate.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/10. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol YMEventsRowViewDelegate: UIScrollViewDelegate { 13 | func eventsRowView(_ view: YMEventsRowView, numberOfEventsAt day: Int) -> Int 14 | func eventsRowView(_ view: YMEventsRowView, rangeForEventAtIndexPath indexPath: IndexPath) -> NSRange 15 | func eventsRowView(_ view: YMEventsRowView, didSelectCellAtIndexPath indexPath: IndexPath) 16 | func eventsRowView(_ view: YMEventsRowView, cellForEventAtIndexPath indexPath: IndexPath) -> YMEventView 17 | } 18 | -------------------------------------------------------------------------------- /YMCalendar/Month/Enum/YMDayLabelAlignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMDayLabelAlignment.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 5/6/17. 6 | // Copyright © 2017 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum YMDayLabelAlignment { 12 | case left, center, right 13 | } 14 | -------------------------------------------------------------------------------- /YMCalendar/Month/Enum/YMDecelerationRate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMDecelerationRate.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/05. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public enum YMDecelerationRate { 13 | case normal, fast 14 | 15 | var value: CGFloat { 16 | switch self { 17 | case .normal: 18 | return UIScrollView.DecelerationRate.normal.rawValue 19 | case .fast: 20 | return UIScrollView.DecelerationRate.fast.rawValue 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /YMCalendar/Month/Enum/YMScrollDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMScrollDirection.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/23. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum YMScrollDirection { 12 | case horizontal 13 | case vertical 14 | } 15 | -------------------------------------------------------------------------------- /YMCalendar/Month/Enum/YMSelectAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMSelectAnimation.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 5/5/17. 6 | // Copyright © 2017 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum YMSelectAnimation { 12 | case none, bounce, fade 13 | } 14 | -------------------------------------------------------------------------------- /YMCalendar/Month/EventKit/EventKitManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventKitManager.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 2017/03/17. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import EventKit 12 | 13 | final public class EventKitManager { 14 | public typealias EventSaveCompletionBlockType = (_ accessGranted: Bool) -> Void 15 | 16 | public var eventStore: EKEventStore 17 | 18 | public init(eventStore: EKEventStore?=nil) { 19 | if let eventStore = eventStore { 20 | self.eventStore = eventStore 21 | } else { 22 | self.eventStore = EKEventStore() 23 | } 24 | } 25 | 26 | public var isGranted: Bool = false 27 | 28 | public func checkEventStoreAccessForCalendar(completion: EventSaveCompletionBlockType?) { 29 | let status = EKEventStore.authorizationStatus(for: .event) 30 | switch status { 31 | case .authorized: 32 | isGranted = true 33 | completion?(isGranted) 34 | case .notDetermined: 35 | requestCalendarAccess(completion: completion) 36 | case .denied, .restricted: 37 | isGranted = false 38 | completion?(isGranted) 39 | } 40 | } 41 | 42 | private func requestCalendarAccess(completion: EventSaveCompletionBlockType?) { 43 | eventStore.requestAccess(to: .event) { [weak self] granted, _ in 44 | self?.isGranted = granted 45 | completion?(granted) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /YMCalendar/Month/Layout/YMCalendarLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarLayout.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/21. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | private enum AttrKey { 13 | case dayCells 14 | case weeks 15 | case months 16 | } 17 | 18 | final class YMCalendarLayout: UICollectionViewLayout { 19 | 20 | typealias AttrDict = [IndexPath: UICollectionViewLayoutAttributes] 21 | 22 | var scrollDirection: YMScrollDirection = .horizontal 23 | 24 | private var layoutAttrDict: [AttrKey: AttrDict] = [:] 25 | 26 | weak var dataSource: YMCalendarLayoutDataSource? 27 | 28 | var dayHeaderHeight: CGFloat = 18.0 29 | 30 | var contentSize: CGSize = .zero 31 | 32 | fileprivate func widthForColumnRange(_ range: NSRange) -> CGFloat { 33 | let availableWidth = collectionView?.bounds.size.width ?? 300 34 | let columnWidth = availableWidth / 7 35 | 36 | if NSMaxRange(range) == 7 { 37 | return availableWidth - columnWidth * CGFloat((7 - range.length)) 38 | } 39 | return columnWidth * CGFloat(range.length) 40 | } 41 | 42 | fileprivate func columnWidth(colIndex: Int) -> CGFloat { 43 | return widthForColumnRange(NSRange(location: colIndex, length: 1)) 44 | } 45 | 46 | override public func prepare() { 47 | super.prepare() 48 | 49 | contentSize = .zero 50 | 51 | guard let collectionView = collectionView else { 52 | return 53 | } 54 | 55 | let numberOfMonths = collectionView.numberOfSections 56 | 57 | var monthsAttrDict: AttrDict = [:] 58 | var weeksAttrDict: AttrDict = [:] 59 | var dayCellsAttrDict: AttrDict = [:] 60 | 61 | var x: CGFloat = 0 62 | var y: CGFloat = 0 63 | 64 | for month in 0.. UICollectionViewLayoutAttributes? { 148 | guard let layout = layoutAttrDict[.dayCells], 149 | let attr = layout[indexPath] else { 150 | return nil 151 | } 152 | return attr 153 | } 154 | 155 | override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 156 | var allAttributes: [UICollectionViewLayoutAttributes] = [] 157 | layoutAttrDict.forEach { (_, attributeDict) in 158 | attributeDict.forEach { (_, attributes) in 159 | if rect.intersects(attributes.frame) { 160 | allAttributes.append(attributes) 161 | } 162 | } 163 | } 164 | return allAttributes 165 | } 166 | 167 | override public func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { 168 | var attributes: UICollectionViewLayoutAttributes? 169 | if elementKind == YMMonthBackgroundView.ym.kind { 170 | if let layout = layoutAttrDict[.months] { 171 | attributes = layout[indexPath] 172 | } 173 | } else if elementKind == YMMonthWeekView.ym.kind { 174 | if let layout = layoutAttrDict[.weeks] { 175 | attributes = layout[indexPath] 176 | } 177 | } 178 | return attributes 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /YMCalendar/Month/Layout/YMCalendarLayoutDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarLayoutDataSource.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/21. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol YMCalendarLayoutDataSource: class { 13 | func collectionView(_ collectionView: UICollectionView, layout: YMCalendarLayout, columnAt indexPath: IndexPath) -> Int 14 | } 15 | -------------------------------------------------------------------------------- /YMCalendar/Month/ViewController/YMCalendarEKViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarEKViewController.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/04/02. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import EventKit 12 | 13 | open class YMCalendarEKViewController: YMCalendarViewController { 14 | typealias EventsDict = [Date: [EKEvent]] 15 | private var cachedEvents: [MonthDate: EventsDict] = [:] 16 | private var queueToLoad: [MonthDate] = [] 17 | private var bgQueue = DispatchQueue(label: "YMCalendarEKViewController.bgQueue") 18 | 19 | private var visibleMonths: [MonthDate] = [] 20 | 21 | public var calendar: Calendar = .current { 22 | didSet { 23 | calendarView.calendar = calendar 24 | } 25 | } 26 | 27 | public let eventKitManager = EventKitManager() 28 | 29 | private var eventStore: EKEventStore { 30 | return eventKitManager.eventStore 31 | } 32 | 33 | private let YMEventStandardViewIdentifier = "YMEventStandardView" 34 | 35 | override open func viewDidLoad() { 36 | super.viewDidLoad() 37 | calendarView.delegate = self 38 | calendarView.dataSource = self 39 | calendarView.registerClass(YMEventStandardView.self, 40 | forEventCellReuseIdentifier: YMEventStandardViewIdentifier) 41 | 42 | eventKitManager.checkEventStoreAccessForCalendar { [weak self] granted in 43 | if granted { 44 | self?.reloadEvents() 45 | } 46 | } 47 | } 48 | 49 | open override func viewDidAppear(_ animated: Bool) { 50 | super.viewDidAppear(animated) 51 | loadEventsIfNeeded() 52 | } 53 | 54 | public func reloadEvents() { 55 | cachedEvents.removeAll() 56 | queueToLoad.removeAll() 57 | visibleMonths.removeAll() 58 | 59 | DispatchQueue.main.async { 60 | self.loadEventsIfNeeded() 61 | } 62 | } 63 | 64 | private func fetchEvents(in month: MonthDate, calendars: [EKCalendar]?) -> [EKEvent] { 65 | let start = calendar.date(from: month) 66 | let end = calendar.nextStartOfMonthForDate(calendar.date(from: month)) 67 | let predicate = eventStore.predicateForEvents(withStart: start, end: end, calendars: calendars) 68 | if eventKitManager.isGranted { 69 | let events = eventStore.events(matching: predicate) 70 | return events 71 | } 72 | return [] 73 | } 74 | 75 | private func loadMonth(_ month: MonthDate) { 76 | let events = fetchEvents(in: month, calendars: nil) 77 | 78 | var eventsDict: EventsDict = [:] 79 | for event in events { 80 | let start = calendar.startOfDay(for: event.startDate) 81 | let eventRange = DateRange(start: start, end: event.endDate) 82 | eventRange.enumerateDaysWithCalendar(calendar) { 83 | if eventsDict[$0] == nil { 84 | eventsDict[$0] = [] 85 | } 86 | eventsDict[$0]?.append(event) 87 | } 88 | } 89 | 90 | cachedEvents[month] = eventsDict 91 | 92 | DispatchQueue.main.async { 93 | self.calendarView.reloadEvents(in: month) 94 | } 95 | } 96 | 97 | private func addMonthsToLoad(months: [MonthDate]) { 98 | let loadMonths = months.filter { month -> Bool in 99 | !cachedEvents.contains(where: { $0.key == month }) 100 | } 101 | queueToLoad.append(contentsOf: loadMonths) 102 | 103 | bgQueue.async { [weak self] in 104 | loadMonths.forEach { 105 | self?.loadMonth($0) 106 | if let idx = self?.queueToLoad.index(of: $0) { 107 | self?.queueToLoad.remove(at: idx) 108 | } 109 | } 110 | } 111 | } 112 | 113 | public func eventsAtDate(_ date: Date) -> [EKEvent] { 114 | if let monthEvents = cachedEvents[calendar.monthDate(from: date)] { 115 | if let events = monthEvents[date] { 116 | return events 117 | } 118 | } 119 | return [] 120 | } 121 | 122 | public func eventAtIndex(_ index: Int, date: Date) -> EKEvent { 123 | return eventsAtDate(date)[index] 124 | } 125 | 126 | public func loadEventsIfNeeded() { 127 | if visibleMonths != calendarView.visibleMonths { 128 | self.visibleMonths = calendarView.visibleMonths 129 | addMonthsToLoad(months: visibleMonths) 130 | } 131 | } 132 | } 133 | 134 | // - MARK: YMCalendarDelegate 135 | extension YMCalendarEKViewController: YMCalendarDelegate { 136 | public func calendarViewDidScroll(_ view: YMCalendarView) { 137 | loadEventsIfNeeded() 138 | } 139 | } 140 | 141 | // - MARK: YMCalendarDataSource 142 | extension YMCalendarEKViewController: YMCalendarDataSource { 143 | public func calendarView(_ view: YMCalendarView, numberOfEventsAtDate date: Date) -> Int { 144 | return eventsAtDate(date).count 145 | } 146 | 147 | public func calendarView(_ view: YMCalendarView, dateRangeForEventAtIndex index: Int, date: Date) -> DateRange? { 148 | let events = eventsAtDate(date) 149 | var range: DateRange? 150 | if index <= events.count { 151 | let event = events[index] 152 | range = DateRange(start: event.startDate, end: event.endDate) 153 | } 154 | return range 155 | } 156 | 157 | open func calendarView(_ view: YMCalendarView, eventViewForEventAtIndex index: Int, date: Date) -> YMEventView { 158 | let events = eventsAtDate(date) 159 | precondition(index <= events.count) 160 | 161 | let event = events[index] 162 | guard let cell = view.dequeueReusableCellWithIdentifier(YMEventStandardViewIdentifier, forEventAtIndex: index, date: date) as? YMEventStandardView else { 163 | fatalError() 164 | } 165 | cell.backgroundColor = UIColor(cgColor: event.calendar.cgColor) 166 | cell.title = event.title 167 | return cell 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /YMCalendar/Month/ViewController/YMCalendarViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarViewController.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/04/02. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import EventKit 11 | 12 | open class YMCalendarViewController: UIViewController { 13 | 14 | public var calendarView: YMCalendarView { 15 | get { 16 | return view as! YMCalendarView 17 | } 18 | set { 19 | view = newValue 20 | } 21 | } 22 | 23 | open override func loadView() { 24 | calendarView = YMCalendarView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /YMCalendar/Month/YMCalendarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/21. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final public class YMCalendarView: UIView, YMCalendarAppearance { 13 | 14 | private lazy var collectionView: UICollectionView = createCollectionView() 15 | 16 | private var reuseQueue = ReusableObjectQueue() 17 | 18 | private lazy var layout: YMCalendarLayout = { 19 | let calendarLayout = YMCalendarLayout() 20 | calendarLayout.dataSource = self 21 | return calendarLayout 22 | }() 23 | 24 | public weak var appearance: YMCalendarAppearance? 25 | 26 | public weak var delegate: YMCalendarDelegate? 27 | 28 | public weak var dataSource: YMCalendarDataSource? 29 | 30 | public var calendar = Calendar.current { 31 | didSet { 32 | reload() 33 | } 34 | } 35 | 36 | override public var backgroundColor: UIColor? { 37 | didSet { 38 | collectionView.backgroundColor = backgroundColor 39 | } 40 | } 41 | 42 | private var gradientLayer = CAGradientLayer() 43 | 44 | public var gradientColors: [UIColor]? { 45 | didSet { 46 | if let colors = gradientColors { 47 | gradientLayer.colors = colors.map {$0.cgColor} 48 | } else { 49 | gradientLayer.colors = nil 50 | } 51 | } 52 | } 53 | 54 | public var gradientLocations: [NSNumber]? { 55 | set { 56 | gradientLayer.locations = newValue 57 | } 58 | get { 59 | return gradientLayer.locations 60 | } 61 | } 62 | 63 | public var gradientStartPoint: CGPoint { 64 | set { 65 | gradientLayer.startPoint = newValue 66 | } 67 | get { 68 | return gradientLayer.startPoint 69 | } 70 | } 71 | 72 | public var gradientEndPoint: CGPoint { 73 | set { 74 | gradientLayer.endPoint = newValue 75 | } 76 | get { 77 | return gradientLayer.endPoint 78 | } 79 | } 80 | 81 | public var allowsMultipleSelection: Bool { 82 | get { 83 | return collectionView.allowsMultipleSelection 84 | } 85 | set { 86 | collectionView.allowsMultipleSelection = newValue 87 | } 88 | } 89 | 90 | public var allowsSelection: Bool { 91 | get { 92 | return collectionView.allowsSelection 93 | } 94 | set { 95 | collectionView.allowsSelection = newValue 96 | } 97 | } 98 | 99 | public var isPagingEnabled: Bool { 100 | get { 101 | return collectionView.isPagingEnabled 102 | } 103 | set { 104 | collectionView.isPagingEnabled = newValue 105 | } 106 | } 107 | 108 | public var scrollDirection: YMScrollDirection = .vertical { 109 | didSet { 110 | layout.scrollDirection = scrollDirection 111 | layout.invalidateLayout() 112 | } 113 | } 114 | 115 | public var decelerationRate: YMDecelerationRate = .normal { 116 | didSet { 117 | collectionView.decelerationRate = UIScrollView.DecelerationRate(rawValue: decelerationRate.value) 118 | } 119 | } 120 | 121 | public var dayLabelHeight: CGFloat = 18 { 122 | didSet { 123 | layout.dayHeaderHeight = dayLabelHeight 124 | collectionView.reloadData() 125 | } 126 | } 127 | 128 | public var monthRange: MonthRange? { 129 | didSet { 130 | if let range = monthRange { 131 | startDate = range.start 132 | } else { 133 | startDate = monthDate(from: Date()) 134 | } 135 | } 136 | } 137 | 138 | private var selectedIndexes: [IndexPath] = [] 139 | 140 | /// Height of event items in EventsRow. Default value is 16. 141 | public var eventViewHeight: CGFloat = 16 142 | 143 | /// Number of visble events in a week row. 144 | /// If value is nil, events can be displayed by scroll. 145 | public var maxVisibleEvents: Int? 146 | 147 | /// Cache of EventsRowViews. EventsRowView belongs to MonthWeekView and 148 | /// has events(UIViews) for a week. This dictionary has start of week 149 | /// as key and events as value. 150 | fileprivate var eventRowsCache = IndexableDictionary() 151 | 152 | /// Capacity of eventRowsCache. 153 | fileprivate let rowCacheSize = 40 154 | 155 | private var maxStartDate: MonthDate? { 156 | guard let range = monthRange else { 157 | return nil 158 | } 159 | return max(range.end.add(month: -numberOfLoadedMonths), range.start) 160 | } 161 | 162 | private func dateFrom(monthDate: MonthDate) -> Date { 163 | return calendar.date(from: monthDate) 164 | } 165 | 166 | private func monthDate(from date: Date) -> MonthDate { 167 | return calendar.monthDate(from: date) 168 | } 169 | 170 | private lazy var startDate = self.monthDate(from: Date()) 171 | 172 | public var visibleMonths: [MonthDate] { 173 | var res: [MonthDate] = [] 174 | if let first = collectionView.indexPathsForVisibleItems.first { 175 | res.append(monthDate(from: dateAt(first))) 176 | } 177 | if let last = collectionView.indexPathsForVisibleItems.last { 178 | let month = monthDate(from: dateAt(last)) 179 | if !res.contains(month) { 180 | res.append(month) 181 | } 182 | } 183 | return res 184 | } 185 | 186 | public var visibleDays: DateRange? { 187 | guard let first = collectionView.indexPathsForVisibleItems.lazy.sorted().first, 188 | let last = collectionView.indexPathsForVisibleItems.lazy.sorted().last else { 189 | return nil 190 | } 191 | return DateRange(start: dateAt(first), end: dateAt(last)) 192 | } 193 | 194 | public var selectAnimation: YMSelectAnimation = .bounce 195 | 196 | public var deselectAnimation: YMSelectAnimation = .fade 197 | 198 | private var selectedEventDate: Date? 199 | 200 | private var selectedEventIndex: Int = 0 201 | 202 | private var displayingMonthDate: Date = Date() 203 | 204 | private var numberOfLoadedMonths: Int { 205 | if let range = monthRange { 206 | return min(range.start.monthDiff(with: range.end), 9) 207 | } 208 | return 9 209 | } 210 | 211 | // MARK: - Initialize 212 | 213 | override init(frame: CGRect) { 214 | super.init(frame: frame) 215 | commonInit() 216 | } 217 | 218 | required public init?(coder aDecoder: NSCoder) { 219 | super.init(coder: aDecoder) 220 | commonInit() 221 | } 222 | 223 | private func commonInit() { 224 | backgroundColor = .white 225 | addSubview(collectionView) 226 | } 227 | 228 | private func createCollectionView() -> UICollectionView { 229 | collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) 230 | collectionView.delegate = self 231 | collectionView.dataSource = self 232 | collectionView.bounces = false 233 | collectionView.showsVerticalScrollIndicator = false 234 | collectionView.showsHorizontalScrollIndicator = false 235 | collectionView.allowsMultipleSelection = false 236 | collectionView.backgroundView = UIView() 237 | collectionView.backgroundView?.layer.insertSublayer(gradientLayer, at: 0) 238 | collectionView.ym.register(YMMonthDayCollectionCell.self) 239 | collectionView.ym.register(YMMonthBackgroundView.self) 240 | collectionView.ym.register(YMMonthWeekView.self) 241 | return collectionView 242 | } 243 | 244 | // MARK: - Layout 245 | 246 | override public func layoutSubviews() { 247 | super.layoutSubviews() 248 | collectionView.frame = bounds 249 | gradientLayer.frame = bounds 250 | recenterIfNeeded() 251 | } 252 | } 253 | 254 | extension YMCalendarView { 255 | 256 | // MARK: - Utils 257 | 258 | private func dateAt(_ indexPath: IndexPath) -> Date { 259 | var comp = DateComponents() 260 | comp.month = indexPath.section 261 | comp.day = indexPath.row 262 | return calendar.date(byAdding: comp, to: dateFrom(monthDate: startDate))! 263 | } 264 | 265 | private func indexPathForDate(_ date: Date) -> IndexPath? { 266 | if let range = monthRange { 267 | guard range.contains(monthDate(from: date)) else { 268 | return nil 269 | } 270 | } 271 | let comps = calendar.dateComponents([.month, .day], from: dateFrom(monthDate: startDate), to: date) 272 | guard let day = comps.day, let month = comps.month else { 273 | return nil 274 | } 275 | return IndexPath(item: day, section: month) 276 | } 277 | 278 | private func startDayAtMonth(in section: Int) -> Date { 279 | return dateAt(IndexPath(item: 0, section: section)) 280 | } 281 | 282 | private func column(at indexPath: IndexPath) -> Int { 283 | let weekday = calendar.component(.weekday, from: dateAt(indexPath)) 284 | return (weekday + 7 - calendar.firstWeekday) % 7 285 | } 286 | 287 | private func dateRangeOf(rowView: YMEventsRowView) -> DateRange? { 288 | guard let start = calendar.date(byAdding: .day, value: rowView.daysRange.location, to: rowView.monthStart), 289 | let end = calendar.date(byAdding: .day, value: NSMaxRange(rowView.daysRange), to: rowView.monthStart) else { 290 | return nil 291 | } 292 | return DateRange(start: start, end: end) 293 | } 294 | 295 | public func reload() { 296 | clearRowsCacheIn(range: nil) 297 | collectionView.reloadData() 298 | } 299 | } 300 | 301 | extension YMCalendarView { 302 | 303 | // MARK: - Public 304 | 305 | public func registerClass(_ objectClass: ReusableObject.Type, forEventCellReuseIdentifier identifier: String) { 306 | reuseQueue.registerClass(objectClass, forObjectWithReuseIdentifier: identifier) 307 | } 308 | 309 | public func dequeueReusableCellWithIdentifier(_ identifier: String, forEventAtIndex index: Int, date: Date) -> T? { 310 | guard let cell = reuseQueue.dequeueReusableObjectWithIdentifier(identifier) as? T? else { 311 | return nil 312 | } 313 | if let selectedDate = selectedEventDate, 314 | calendar.isDate(selectedDate, inSameDayAs: date) && index == selectedEventIndex { 315 | cell?.selected = true 316 | } 317 | return cell 318 | } 319 | 320 | public func reloadEvents() { 321 | eventRowsCache.forEach { 322 | $0.value.reload() 323 | } 324 | } 325 | 326 | public func reloadEventsAtDate(_ date: Date) { 327 | eventRowsCache.first(where: { $0.key == date })?.value.reload() 328 | } 329 | 330 | public func reloadEventsInRange(_ range: DateRange) { 331 | eventRowsCache 332 | .filter { 333 | dateRangeOf(rowView: $0.value)?.intersectsDateRange(range) ?? false 334 | }.forEach { 335 | $0.value.reload() 336 | } 337 | } 338 | 339 | public func reloadEvents(in monthDate: MonthDate) { 340 | eventRowsCache 341 | .filter { self.monthDate(from: $0.key) == monthDate } 342 | .forEach { $0.value.reload() } 343 | } 344 | 345 | public var visibleEventViews: [UIView] { 346 | return visibleEventRows.flatMap { 347 | $0.viewsInRect($0.convert(bounds, to: self)) 348 | } 349 | } 350 | 351 | public func eventViewForEventAtIndex(_ index: Int, date: Date) -> UIView? { 352 | for rowView in visibleEventRows { 353 | guard let day = calendar.dateComponents([.day], from: rowView.monthStart, to: date).day else { 354 | return nil 355 | } 356 | if NSLocationInRange(day, rowView.daysRange) { 357 | return rowView.eventView(at: IndexPath(item: index, section: day)) 358 | } 359 | } 360 | return nil 361 | } 362 | 363 | public func eventCellAtPoint(_ pt: CGPoint, date: inout Date, index: inout Int) -> UIView? { 364 | for rowView in visibleEventRows { 365 | let ptInRow = rowView.convert(pt, from: self) 366 | if let path = rowView.indexPathForCellAtPoint(ptInRow) { 367 | var comps = DateComponents() 368 | comps.day = path.section 369 | date = calendar.date(byAdding: comps, to: rowView.monthStart)! 370 | index = path.item 371 | return rowView.eventView(at: path) 372 | } 373 | } 374 | return nil 375 | } 376 | } 377 | 378 | extension YMCalendarView { 379 | 380 | // MARK: - Scrolling 381 | 382 | public func scrollToDate(_ date: Date, animated: Bool) { 383 | let month = monthDate(from: date) 384 | if let range = monthRange, !range.contains(month) { 385 | return 386 | } 387 | 388 | let offset = offsetForMonth(month) 389 | if scrollDirection == .vertical { 390 | collectionView.setContentOffset(CGPoint(x: 0, y: offset), animated: animated) 391 | } else { 392 | collectionView.setContentOffset(CGPoint(x: offset, y: 0), animated: animated) 393 | } 394 | 395 | delegate?.calendarViewDidScroll?(self) 396 | } 397 | 398 | private func offsetForMonth(_ monthDate: MonthDate) -> CGFloat { 399 | let diff = startDate.monthDiff(with: monthDate) 400 | let size = scrollDirection == .vertical ? bounds.height : bounds.width 401 | return size * CGFloat(diff) 402 | } 403 | 404 | private func monthFromOffset(_ offset: CGFloat) -> MonthDate { 405 | var month = startDate 406 | if scrollDirection == .vertical { 407 | let height = bounds.height 408 | var y = offset > 0 ? height : 0 409 | 410 | while y < abs(offset) { 411 | month = month.add(month: offset > 0 ? 1 : -1) 412 | y += height 413 | } 414 | } else { 415 | let width = bounds.width 416 | var x = offset > 0 ? width : 0 417 | 418 | while x < abs(offset) { 419 | month = month.add(month: offset > 0 ? 1 : -1) 420 | x += width 421 | } 422 | } 423 | return month 424 | } 425 | 426 | private func adjustStartDateToCenter(date: MonthDate) -> Int { 427 | let offset = (numberOfLoadedMonths - 1) / 2 428 | let start = date.add(month: -offset) 429 | var s = start 430 | if let range = monthRange, let maxStartDate = maxStartDate { 431 | // monthRange.start <= start <= maxStartDate 432 | if start < range.start { 433 | s = range.start 434 | } else if start > maxStartDate { 435 | s = maxStartDate 436 | } 437 | } 438 | let diff = startDate.monthDiff(with: s) 439 | self.startDate = s 440 | return diff 441 | } 442 | 443 | private func recenterIfNeeded() { 444 | let offset, content, bounds, boundsMax: CGFloat 445 | switch scrollDirection { 446 | case .vertical: 447 | offset = max(collectionView.contentOffset.y, 0) 448 | content = collectionView.contentSize.height 449 | bounds = collectionView.bounds.height 450 | boundsMax = collectionView.bounds.maxY 451 | case .horizontal: 452 | offset = max(collectionView.contentOffset.x, 0) 453 | content = collectionView.contentSize.width 454 | bounds = collectionView.bounds.width 455 | boundsMax = collectionView.bounds.maxX 456 | } 457 | 458 | guard content > 0 else { 459 | return 460 | } 461 | 462 | if offset < bounds || boundsMax + bounds > content { 463 | let oldStart = startDate 464 | 465 | let centerMonth = monthFromOffset(offset) 466 | let monthOffset = adjustStartDateToCenter(date: centerMonth) 467 | 468 | if monthOffset != 0 { 469 | let k = offsetForMonth(oldStart) 470 | collectionView.reloadData() 471 | 472 | var contentOffset = collectionView.contentOffset 473 | switch scrollDirection { 474 | case .vertical: 475 | contentOffset.y = k + offset 476 | case .horizontal: 477 | contentOffset.x = k + offset 478 | } 479 | collectionView.contentOffset = contentOffset 480 | } 481 | } 482 | } 483 | } 484 | 485 | extension YMCalendarView { 486 | // MARK: - Rows Handling 487 | 488 | private func removeRowCache(at date: Date) { 489 | eventRowsCache.removeValue(forKey: date) 490 | } 491 | 492 | private var visibleEventRows: [YMEventsRowView] { 493 | guard let visibleRange = visibleDays else { 494 | return [] 495 | } 496 | return eventRowsCache 497 | .filter { 498 | visibleRange.contains(date: $0.key) 499 | }.map { 500 | $0.value 501 | } 502 | } 503 | 504 | private func clearRowsCacheIn(range: DateRange?) { 505 | if let range = range { 506 | eventRowsCache 507 | .filter { range.contains(date: $0.key) } 508 | .forEach { removeRowCache(at: $0.key) } 509 | } else { 510 | eventRowsCache.forEach { removeRowCache(at: $0.key) } 511 | } 512 | } 513 | 514 | private func eventsRowView(at rowStart: Date) -> YMEventsRowView { 515 | var eventsRowView = eventRowsCache.value(forKey: rowStart) 516 | if eventsRowView == nil { 517 | eventsRowView = YMEventsRowView() 518 | let startOfMonth = calendar.startOfMonthForDate(rowStart) 519 | let first = calendar.dateComponents([.day], from: startOfMonth, to: rowStart).day 520 | if let range = calendar.range(of: .day, in: .weekOfMonth, for: rowStart) { 521 | let numDays = range.upperBound - range.lowerBound 522 | 523 | eventsRowView?.monthStart = startOfMonth 524 | eventsRowView?.maxVisibleLines = maxVisibleEvents 525 | eventsRowView?.itemHeight = eventViewHeight 526 | eventsRowView?.eventsRowDelegate = self 527 | eventsRowView?.daysRange = NSRange(location: first!, length: numDays) 528 | eventsRowView?.dayWidth = bounds.width / 7 529 | 530 | eventsRowView?.reload() 531 | } 532 | cacheRow(eventsRowView!, forDate: rowStart) 533 | } 534 | return eventsRowView! 535 | } 536 | 537 | private func cacheRow(_ eventsView: YMEventsRowView, forDate date: Date) { 538 | eventRowsCache.updateValue(eventsView, forKey: date) 539 | 540 | if eventRowsCache.count >= rowCacheSize { 541 | if let first = eventRowsCache.first?.0 { 542 | removeRowCache(at: first) 543 | } 544 | } 545 | } 546 | 547 | private func monthRowView(at indexPath: IndexPath) -> YMMonthWeekView { 548 | var weekView: YMMonthWeekView! 549 | while true { 550 | let v = collectionView.ym.dequeue(YMMonthWeekView.self, for: indexPath) 551 | if !visibleEventRows.contains(v.eventsView) { 552 | weekView = v 553 | break 554 | } 555 | } 556 | 557 | weekView.eventsView = eventsRowView(at: dateAt(indexPath)) 558 | return weekView 559 | } 560 | } 561 | 562 | extension YMCalendarView: UICollectionViewDataSource { 563 | // MARK: - UICollectionViewDataSource 564 | 565 | public func numberOfSections(in collectionView: UICollectionView) -> Int { 566 | return numberOfLoadedMonths 567 | } 568 | 569 | public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 570 | let startDay = startDayAtMonth(in: section) 571 | return calendar.numberOfDaysInMonth(date: startDay) 572 | } 573 | 574 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 575 | let appearance = self.appearance ?? self 576 | 577 | let cell = collectionView.ym.dequeue(YMMonthDayCollectionCell.self, for: indexPath) 578 | let date = dateAt(indexPath) 579 | let font = appearance.calendarViewAppearance(self, dayLabelFontAtDate: date) 580 | cell.day = calendar.day(date) 581 | cell.dayLabel.font = font 582 | cell.dayLabelAlignment = appearance.dayLabelAlignment(in: self) 583 | cell.dayLabelColor = appearance.calendarViewAppearance(self, dayLabelTextColorAtDate: date) 584 | cell.dayLabelBackgroundColor = appearance.calendarViewAppearance(self, dayLabelBackgroundColorAtDate: date) 585 | cell.dayLabelSelectedColor = appearance.calendarViewAppearance(self, dayLabelSelectedTextColorAtDate: date) 586 | cell.dayLabelSelectedBackgroundColor = appearance.calendarViewAppearance(self, dayLabelSelectedBackgroundColorAtDate: date) 587 | cell.dayLabelHeight = dayLabelHeight 588 | 589 | // select cells which already selected dates 590 | if selectedIndexes.contains(indexPath) { 591 | cell.select(withAnimation: .none) 592 | } 593 | return cell 594 | } 595 | 596 | public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 597 | switch kind { 598 | case YMMonthBackgroundView.ym.kind: 599 | return backgroundView(at: indexPath) 600 | case YMMonthWeekView.ym.kind: 601 | return monthRowView(at: indexPath) 602 | default: 603 | fatalError() 604 | } 605 | } 606 | 607 | private func backgroundView(at indexPath: IndexPath) -> UICollectionReusableView { 608 | let date = startDayAtMonth(in: indexPath.section) 609 | 610 | let lastColumn = column(at: IndexPath(item: 0, section: indexPath.section + 1)) 611 | let numRows = calendar.numberOfWeeksInMonth(date: date) 612 | 613 | let view = collectionView.ym.dequeue(YMMonthBackgroundView.self, for: indexPath) 614 | view.lastColumn = lastColumn 615 | view.numberOfRows = numRows 616 | 617 | let viewAppearance = appearance ?? self 618 | view.horizontalGridColor = viewAppearance.horizontalGridColor(in: self) 619 | view.horizontalGridWidth = viewAppearance.horizontalGridWidth(in: self) 620 | view.verticalGridColor = viewAppearance.verticalGridColor(in: self) 621 | view.verticalGridWidth = viewAppearance.verticalGridWidth(in: self) 622 | 623 | view.setNeedsDisplay() 624 | 625 | return view 626 | } 627 | } 628 | 629 | extension YMCalendarView: YMEventsRowViewDelegate { 630 | // MARK: - YMEventsRowViewDelegate 631 | 632 | func eventsRowView(_ view: YMEventsRowView, numberOfEventsAt day: Int) -> Int { 633 | var comps = DateComponents() 634 | comps.day = day 635 | guard let date = calendar.date(byAdding: comps, to: view.monthStart), 636 | let count = dataSource?.calendarView(self, numberOfEventsAtDate: date) else { 637 | return 0 638 | } 639 | return count 640 | } 641 | 642 | func eventsRowView(_ view: YMEventsRowView, rangeForEventAtIndexPath indexPath: IndexPath) -> NSRange { 643 | var comps = DateComponents() 644 | comps.day = indexPath.section 645 | guard let date = calendar.date(byAdding: comps, to: view.monthStart), 646 | let dateRange = dataSource?.calendarView(self, dateRangeForEventAtIndex: indexPath.item, date: date) else { 647 | return NSRange() 648 | } 649 | 650 | let start = max(0, calendar.dateComponents([.day], from: view.monthStart, to: dateRange.start).day!) 651 | var end = calendar.dateComponents([.day], from: view.monthStart, to: dateRange.end).day! 652 | if dateRange.end.timeIntervalSince(calendar.startOfDay(for: dateRange.end)) >= 0 { 653 | end += 1 654 | } 655 | end = min(end, NSMaxRange(view.daysRange)) 656 | return NSRange(location: start, length: end - start) 657 | } 658 | 659 | func eventsRowView(_ view: YMEventsRowView, cellForEventAtIndexPath indexPath: IndexPath) -> YMEventView { 660 | var comps = DateComponents() 661 | comps.day = indexPath.section 662 | guard let date = calendar.date(byAdding: comps, to: view.monthStart), 663 | let eventView = dataSource?.calendarView(self, eventViewForEventAtIndex: indexPath.item, date: date) else { 664 | fatalError() 665 | } 666 | return eventView 667 | } 668 | 669 | func eventsRowView(_ view: YMEventsRowView, didSelectCellAtIndexPath indexPath: IndexPath) { 670 | var comps = DateComponents() 671 | comps.day = indexPath.section 672 | guard let date = calendar.date(byAdding: comps, to: view.monthStart) else { 673 | return 674 | } 675 | 676 | selectedEventDate = date 677 | selectedEventIndex = indexPath.item 678 | 679 | delegate?.calendarView?(self, didSelectEventAtIndex: indexPath.item, date: date) 680 | } 681 | } 682 | 683 | extension YMCalendarView: YMCalendarLayoutDataSource { 684 | 685 | // MARK: - YMCalendarLayoutDelegate 686 | 687 | func collectionView(_ collectionView: UICollectionView, layout: YMCalendarLayout, columnAt indexPath: IndexPath) -> Int { 688 | return column(at: indexPath) 689 | } 690 | } 691 | 692 | extension YMCalendarView: UICollectionViewDelegate { 693 | // MARK: - public 694 | 695 | /// Select cell item from date manually. 696 | public func selectDayCell(at date: Date) { 697 | // select cells 698 | guard let indexPath = indexPathForDate(date) else { return } 699 | collectionView.selectItem(at: indexPath, animated: false, scrollPosition: UICollectionView.ScrollPosition(rawValue: 0)) 700 | collectionView(collectionView, didSelectItemAt: indexPath) 701 | } 702 | 703 | /// Deselect cell item of selecting indexPath manually. 704 | public func deselectDayCells() { 705 | // deselect cells 706 | collectionView.indexPathsForSelectedItems?.forEach { 707 | collectionView.deselectItem(at: $0, animated: false) 708 | } 709 | } 710 | 711 | // MARK: - UICollectionViewDelegate 712 | private func cellForItem(at indexPath: IndexPath) -> YMMonthDayCollectionCell? { 713 | return collectionView.cellForItem(at: indexPath) as? YMMonthDayCollectionCell 714 | } 715 | 716 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 717 | if allowsMultipleSelection { 718 | if let i = selectedIndexes.index(of: indexPath) { 719 | cellForItem(at: indexPath)?.deselect(withAnimation: deselectAnimation) 720 | selectedIndexes.remove(at: i) 721 | } else { 722 | cellForItem(at: indexPath)?.select(withAnimation: selectAnimation) 723 | selectedIndexes.append(indexPath) 724 | } 725 | } else { 726 | selectedIndexes.forEach { 727 | cellForItem(at: $0)?.deselect(withAnimation: deselectAnimation) 728 | } 729 | cellForItem(at: indexPath)?.select(withAnimation: selectAnimation) 730 | selectedIndexes = [indexPath] 731 | } 732 | 733 | delegate?.calendarView?(self, didSelectDayCellAtDate: dateAt(indexPath)) 734 | } 735 | 736 | // MARK: - UIScrollViewDelegate 737 | 738 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 739 | recenterIfNeeded() 740 | 741 | if let indexPath = collectionView.indexPathForItem(at: center) { 742 | let date = dateAt(indexPath) 743 | let startMonth = calendar.startOfMonthForDate(date) 744 | if displayingMonthDate != startMonth { 745 | displayingMonthDate = startMonth 746 | delegate?.calendarView?(self, didMoveMonthOfStartDate: startMonth) 747 | } 748 | } 749 | 750 | delegate?.calendarViewDidScroll?(self) 751 | } 752 | } 753 | -------------------------------------------------------------------------------- /YMCalendar/Utils/DateRange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateRange.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/02/23. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct DateRange { 12 | public var start: Date 13 | public var end: Date 14 | 15 | public init(start: Date = Date(), end: Date = Date()) { 16 | if start.compare(end) == .orderedDescending { 17 | fatalError("start and end are not ordered ascendingly") 18 | } 19 | self.start = start 20 | self.end = end 21 | } 22 | 23 | public func contains(date: Date) -> Bool { 24 | return date.compare(start) != .orderedAscending && date.compare(end) == .orderedAscending 25 | } 26 | 27 | public func intersectsDateRange(_ range: DateRange) -> Bool { 28 | return !(range.end.compare(start) != .orderedDescending || end.compare(range.start) != .orderedDescending) 29 | } 30 | 31 | public func enumerateDaysWithCalendar(_ calendar: Calendar, block: @escaping ((Date) -> Void)) { 32 | var comp = DateComponents() 33 | comp.day = 1 34 | 35 | var date = start 36 | 37 | while date < end { 38 | block(date) 39 | if let d = calendar.date(byAdding: comp, to: start), let day = comp.day { 40 | date = d 41 | comp.day = day + 1 42 | } 43 | } 44 | } 45 | } 46 | 47 | extension DateRange: Equatable { 48 | public static func ==(lhs: DateRange, rhs: DateRange) -> Bool { 49 | return lhs.start == rhs.start && lhs.end == rhs.end 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /YMCalendar/Utils/IndexableDictionry/IndexableDictionary+Description.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexableDictionary+Description.swift 3 | // IndexableDictionary 4 | // 5 | // Created by Yuma Matsune on 2017/04/28. 6 | // Copyright © 2017年 matsune. All rights reserved. 7 | // 8 | 9 | /// - MARK: CustomStringConvertibles 10 | 11 | extension IndexableDictionary: CustomStringConvertible { 12 | public var description: String { 13 | return makeDescription(debug: false) 14 | } 15 | } 16 | 17 | extension IndexableDictionary: CustomDebugStringConvertible { 18 | public var debugDescription: String { 19 | return makeDescription(debug: true) 20 | } 21 | } 22 | 23 | extension IndexableDictionary { 24 | fileprivate func makeDescription(debug: Bool) -> String { 25 | if isEmpty { return "[:]" } 26 | 27 | let printFunction: (Any, inout String) -> Void = { 28 | if debug { 29 | return { debugPrint($0, separator: "", terminator: "", to: &$1) } 30 | } else { 31 | return { print($0, separator: "", terminator: "", to: &$1) } 32 | } 33 | }() 34 | 35 | let descriptionForItem: (Any) -> String = { item in 36 | var description = "" 37 | printFunction(item, &description) 38 | return description 39 | } 40 | 41 | let bodyComponents = map { element in 42 | return descriptionForItem(element.key) + ": " + descriptionForItem(element.value) 43 | } 44 | 45 | let body = bodyComponents.joined(separator: ", ") 46 | 47 | return "[\(body)]" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /YMCalendar/Utils/IndexableDictionry/IndexableDictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IndexableDictionary.swift 3 | // IndexableDictionary 4 | // 5 | // Created by Yuma Matsune on 2017/04/26. 6 | // Copyright © 2017年 matsune. All rights reserved. 7 | // 8 | 9 | /** 10 | 11 | IndexableDictionary is a structure which combines both features of `Array` and `Dictionary`. 12 | It can hold `key` and `value` pairs and access to them like dictionary. 13 | And it has indices of key-value pairs like array, so you can get values by 14 | array methods and subscripts. 15 | 16 | https://github.com/matsune/IndexableDictionary 17 | 18 | */ 19 | public struct IndexableDictionary: RandomAccessCollection, ExpressibleByArrayLiteral, RangeReplaceableCollection, BidirectionalCollection { 20 | 21 | // MARK: - Type Aliases 22 | 23 | public typealias Element = (key: Key, value: Value) 24 | 25 | public typealias SubSequence = IndexableDictionary 26 | 27 | public typealias Index = Int 28 | 29 | public typealias Indices = CountableRange 30 | 31 | fileprivate var _elements: [Element] 32 | 33 | // MARK: - Initializer 34 | 35 | public init() { 36 | _elements = [] 37 | } 38 | 39 | public init(arrayLiteral elements: Element...) { 40 | _elements = elements 41 | } 42 | 43 | public init(_ elements: [Element]) { 44 | _elements = elements 45 | } 46 | 47 | // MARK: - Subscript 48 | 49 | public subscript(position: Int) -> Element { 50 | get { return _elements[position] } 51 | set { _elements[position] = newValue } 52 | } 53 | 54 | public subscript(bounds: Range) -> SubSequence { 55 | let _array = (bounds.lowerBound.. Element in 56 | return self[index] 57 | } 58 | return IndexableDictionary(_array) 59 | } 60 | 61 | // MARK: - Indices 62 | 63 | public var startIndex: Index { return _elements.startIndex } 64 | 65 | public var endIndex: Index { return _elements.endIndex } 66 | 67 | public func index(forKey key: Key) -> Index? { 68 | return _elements.index(where: {$0.0 == key}) 69 | } 70 | 71 | // MARK: - Range Replace 72 | 73 | public mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C: Collection, C.Iterator.Element == IndexableDictionary.Iterator.Element { 74 | _elements.replaceSubrange(subrange, with: newElements) 75 | } 76 | 77 | // MARK: - Dictionary property 78 | 79 | public var keys: LazyMapCollection { return lazy.map {$0.key} } 80 | 81 | public var values: LazyMapCollection { return lazy.map {$0.value} } 82 | 83 | public func value(forKey key: Key) -> Value? { 84 | return _elements.first(where: {$0.key == key})?.value 85 | } 86 | 87 | @discardableResult 88 | public mutating func removeValue(forKey key: Key) -> Value? { 89 | guard let index = index(forKey: key) else { return nil } 90 | return remove(at: index).value 91 | } 92 | 93 | @discardableResult 94 | public mutating func updateValue(_ newValue: Value, forKey key: Key) -> Value? { 95 | guard let oldValue = value(forKey: key), let index = index(forKey: key) else { 96 | append((key, newValue)) 97 | return nil 98 | } 99 | self[index].value = newValue 100 | return oldValue 101 | } 102 | } 103 | 104 | // MARK: - Operator 105 | 106 | extension IndexableDictionary where Value: Equatable { 107 | public static func ==(lhs: IndexableDictionary, rhs: IndexableDictionary) -> Bool { 108 | return lhs.keys.elementsEqual(rhs.keys) && lhs.values.elementsEqual(rhs.values) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /YMCalendar/Utils/MonthDate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonthDate.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/20. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct MonthDate { 12 | public let year: Int 13 | public let month: Int 14 | 15 | public init(year: Int, month: Int) { 16 | guard year >= 0 && 1...12 ~= month else { 17 | fatalError("Invalid year or month") 18 | } 19 | self.year = year 20 | self.month = month 21 | } 22 | 23 | public func add(month: Int) -> MonthDate { 24 | let plusYear = month / 12 25 | let plusMonth = month % 12 26 | var y = self.year + plusYear 27 | var m = self.month + plusMonth 28 | if m > 12 { 29 | y += 1 30 | m = m - 12 31 | } else if m < 1 { 32 | y -= 1 33 | m = 12 + m 34 | } 35 | return MonthDate(year: y, month: m) 36 | } 37 | 38 | public func monthDiff(with other: MonthDate) -> Int { 39 | let yDiff = other.year - year 40 | let mDiff = other.month - month 41 | return yDiff * 12 + mDiff 42 | } 43 | } 44 | 45 | extension MonthDate: Comparable { 46 | public static func ==(lhs: MonthDate, rhs: MonthDate) -> Bool { 47 | return lhs.hashValue == rhs.hashValue 48 | } 49 | 50 | public static func <(lhs: MonthDate, rhs: MonthDate) -> Bool { 51 | return lhs.hashValue < rhs.hashValue 52 | } 53 | } 54 | 55 | extension MonthDate: Hashable { 56 | public var hashValue: Int { 57 | return year * 12 + month 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /YMCalendar/Utils/MonthRange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MonthRange.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2018/03/20. 6 | // Copyright © 2018年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct MonthRange { 12 | public let start: MonthDate 13 | public let end: MonthDate 14 | 15 | public init(start: MonthDate, end: MonthDate) { 16 | guard start <= end else { 17 | fatalError("start and end are not ordered ascendingly") 18 | } 19 | self.start = start 20 | self.end = end 21 | } 22 | 23 | public func contains(_ date: MonthDate) -> Bool { 24 | let s = start.year * 12 + start.month 25 | let e = end.year * 12 + end.month 26 | let d = date.year * 12 + date.month 27 | return s...e ~= d 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /YMCalendar/WeekBar/YMCalendarWeekBarAppearance.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarWeekBarAppearance.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/25. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public protocol YMCalendarWeekBarAppearance: class { 13 | func weekBarHorizontalGridColor(in view: YMCalendarWeekBarView) -> UIColor 14 | func weekBarHorizontalGridWidth(in view: YMCalendarWeekBarView) -> CGFloat 15 | func weekBarVerticalGridColor(in view: YMCalendarWeekBarView) -> UIColor 16 | func weekBarVerticalGridWidth(in view: YMCalendarWeekBarView) -> CGFloat 17 | 18 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, textAtWeekday weekday: Int) -> String 19 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, textColorAtWeekday weekday: Int) -> UIColor 20 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, backgroundColorAtWeekday weekday: Int) -> UIColor 21 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, fontAtWeekday weekday: Int) -> UIFont 22 | } 23 | 24 | extension YMCalendarWeekBarAppearance { 25 | public func weekBarHorizontalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 26 | return .black 27 | } 28 | 29 | public func weekBarHorizontalGridWidth(in view: YMCalendarWeekBarView) -> CGFloat { 30 | return 0.3 31 | } 32 | 33 | public func weekBarVerticalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 34 | return .black 35 | } 36 | 37 | public func weekBarVerticalGridWidth(in view: YMCalendarWeekBarView) -> CGFloat { 38 | return 0.3 39 | } 40 | 41 | public func calendarWeekBarView(_ view: YMCalendarWeekBarView, textAtWeekday weekday: Int) -> String { 42 | let symbols = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] 43 | return symbols[weekday - 1] 44 | } 45 | 46 | public func calendarWeekBarView(_ view: YMCalendarWeekBarView, textColorAtWeekday weekday: Int) -> UIColor { 47 | return .black 48 | } 49 | 50 | public func calendarWeekBarView(_ view: YMCalendarWeekBarView, backgroundColorAtWeekday weekday: Int) -> UIColor { 51 | return .clear 52 | } 53 | 54 | public func calendarWeekBarView(_ view: YMCalendarWeekBarView, fontAtWeekday weekday: Int) -> UIFont { 55 | return .systemFont(ofSize: 12.0) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /YMCalendar/WeekBar/YMCalendarWeekBarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendarWeekBarView.swift 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/25. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class YMCalendarWeekBarView: UIView, YMCalendarWeekBarAppearance { 13 | 14 | public var appearance: YMCalendarWeekBarAppearance? 15 | 16 | public var calendar = Calendar.current { 17 | didSet { 18 | setNeedsLayout() 19 | } 20 | } 21 | 22 | private var symbolLabels: [UILabel] = [] 23 | 24 | public var gradientColors: [UIColor]? 25 | 26 | public var gradientLocations: [CGFloat] = [0.0, 1.0] 27 | 28 | public var gradientStartPoint = CGPoint(x: 0.5, y: 0.0) 29 | 30 | public var gradientEndPoint = CGPoint(x: 0.5, y: 1.0) 31 | 32 | override init(frame: CGRect) { 33 | super.init(frame: frame) 34 | commonInit() 35 | } 36 | 37 | required public init?(coder aDecoder: NSCoder) { 38 | super.init(coder: aDecoder) 39 | commonInit() 40 | } 41 | 42 | private func commonInit() { 43 | backgroundColor = .white 44 | 45 | for _ in 0..<7 { 46 | let label = UILabel() 47 | label.textAlignment = .center 48 | label.lineBreakMode = .byClipping 49 | symbolLabels.append(label) 50 | addSubview(label) 51 | } 52 | } 53 | 54 | public override func layoutSubviews() { 55 | super.layoutSubviews() 56 | 57 | let colWidth = bounds.width / 7 58 | let firstWeekday = calendar.firstWeekday 59 | for i in 0.. 7 { 62 | weekday %= 7 63 | } 64 | 65 | let x = CGFloat(i) * colWidth + colWidth / 2 66 | let y = bounds.height / 2 67 | let center = CGPoint(x: x, y: y) 68 | symbolLabels[i].frame.size = CGSize(width: colWidth, height: bounds.height) 69 | symbolLabels[i].center = center 70 | let appearance = self.appearance ?? self 71 | symbolLabels[i].text = appearance.calendarWeekBarView(self, textAtWeekday: weekday) 72 | symbolLabels[i].textColor = appearance.calendarWeekBarView(self, textColorAtWeekday: weekday) 73 | symbolLabels[i].backgroundColor = appearance.calendarWeekBarView(self, backgroundColorAtWeekday: weekday) 74 | symbolLabels[i].font = appearance.calendarWeekBarView(self, fontAtWeekday: weekday) 75 | } 76 | } 77 | 78 | override public func draw(_ rect: CGRect) { 79 | super.draw(rect) 80 | 81 | let c = UIGraphicsGetCurrentContext() 82 | 83 | if let gradientColors = gradientColors { 84 | let startColor = gradientColors[0].cgColor 85 | let endColor = gradientColors[1].cgColor 86 | let colors = [startColor, endColor] as CFArray 87 | 88 | let locations = gradientLocations 89 | 90 | let space = CGColorSpaceCreateDeviceRGB() 91 | 92 | let gradient = CGGradient(colorsSpace: space, colors: colors, locations: locations)! 93 | let startPoint = CGPoint(x: rect.width * gradientStartPoint.x, y: rect.height * gradientStartPoint.y) 94 | let endPoint = CGPoint(x: rect.width * gradientEndPoint.x, y: rect.height * gradientEndPoint.y) 95 | c?.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: []) 96 | } 97 | 98 | let colWidth = rect.width / 7 99 | let rowHeight = rect.height 100 | 101 | var x1: CGFloat 102 | var x2: CGFloat 103 | var y1: CGFloat 104 | var y2: CGFloat 105 | 106 | let appearance = self.appearance ?? self 107 | 108 | let horizontalGridWidth = appearance.weekBarHorizontalGridWidth(in: self) 109 | if horizontalGridWidth > 0 { 110 | c?.setStrokeColor(appearance.weekBarHorizontalGridColor(in: self).cgColor) 111 | c?.setLineWidth(horizontalGridWidth) 112 | c?.beginPath() 113 | 114 | for y in 0...2 { 115 | x1 = 0 116 | x2 = rect.width 117 | y1 = rowHeight * CGFloat(y) 118 | y2 = y1 119 | 120 | c?.move(to: CGPoint(x: x1, y: y1)) 121 | c?.addLine(to: CGPoint(x: x2, y: y2)) 122 | } 123 | c?.strokePath() 124 | } 125 | 126 | let verticalGridWidth = appearance.weekBarVerticalGridWidth(in: self) 127 | if verticalGridWidth > 0 { 128 | c?.setStrokeColor(appearance.weekBarVerticalGridColor(in: self).cgColor) 129 | c?.setLineWidth(verticalGridWidth) 130 | c?.beginPath() 131 | 132 | for x in 0...7 { 133 | x1 = colWidth * CGFloat(x) 134 | x2 = x1 135 | y1 = 0 136 | y2 = rect.height 137 | 138 | c?.move(to: CGPoint(x: x1, y: y1)) 139 | c?.addLine(to: CGPoint(x: x2, y: y2)) 140 | } 141 | c?.strokePath() 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /YMCalendar/YMCalendar.h: -------------------------------------------------------------------------------- 1 | // 2 | // YMCalendar.h 3 | // YMCalendar 4 | // 5 | // Created by Yuma Matsune on 2017/03/10. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for YMCalendar. 12 | FOUNDATION_EXPORT double YMCalendarVersionNumber; 13 | 14 | //! Project version string for YMCalendar. 15 | FOUNDATION_EXPORT const unsigned char YMCalendarVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /YMCalendarDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 2017/03/10. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 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 applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /YMCalendarDemo/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 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "1x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "29x29", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "40x40", 72 | "scale" : "1x" 73 | }, 74 | { 75 | "idiom" : "ipad", 76 | "size" : "40x40", 77 | "scale" : "2x" 78 | }, 79 | { 80 | "idiom" : "ipad", 81 | "size" : "76x76", 82 | "scale" : "1x" 83 | }, 84 | { 85 | "idiom" : "ipad", 86 | "size" : "76x76", 87 | "scale" : "2x" 88 | }, 89 | { 90 | "idiom" : "ipad", 91 | "size" : "83.5x83.5", 92 | "scale" : "2x" 93 | }, 94 | { 95 | "idiom" : "ios-marketing", 96 | "size" : "1024x1024", 97 | "scale" : "1x" 98 | } 99 | ], 100 | "info" : { 101 | "version" : 1, 102 | "author" : "xcode" 103 | } 104 | } -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@2x.png -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-29@3x.png -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matsune/YMCalendar/2f9b3d35255c99f1724de46bcb5689328124eaa0/YMCalendarDemo/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /YMCalendarDemo/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 | 27 | 28 | -------------------------------------------------------------------------------- /YMCalendarDemo/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /YMCalendarDemo/BasicViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicViewController.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 2017/04/23. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import YMCalendar 12 | 13 | final class BasicViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { 14 | 15 | @IBOutlet weak var calendarWeekBarView: YMCalendarWeekBarView! 16 | @IBOutlet weak var calendarView: YMCalendarView! 17 | 18 | let symbols = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] 19 | var calendar = Calendar.current 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | /// WeekBarView 25 | calendarWeekBarView.appearance = self 26 | calendarWeekBarView.calendar = calendar 27 | calendarWeekBarView.backgroundColor = .dark 28 | 29 | /// MonthCalendar 30 | 31 | // Delegates 32 | calendarView.delegate = self 33 | calendarView.dataSource = self 34 | calendarView.appearance = self 35 | 36 | // Month calendar settings 37 | calendarView.calendar = calendar 38 | calendarView.backgroundColor = .dark 39 | calendarView.scrollDirection = .vertical 40 | calendarView.isPagingEnabled = true 41 | 42 | // Events settings 43 | calendarView.eventViewHeight = 14 44 | calendarView.registerClass(YMEventStandardView.self, 45 | forEventCellReuseIdentifier: "YMEventStandardView") 46 | } 47 | 48 | @IBAction func allowsMultipleSelectSwitchChanged(_ sender: UISwitch) { 49 | calendarView.allowsMultipleSelection = sender.isOn 50 | } 51 | 52 | // firstWeekday picker 53 | 54 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 55 | return 7 56 | } 57 | 58 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 59 | return symbols[row] 60 | } 61 | 62 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 63 | return 1 64 | } 65 | 66 | func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { 67 | let title = symbols[row] 68 | let attrString = NSAttributedString(string: title, attributes: [NSAttributedString.Key.foregroundColor: UIColor.white]) 69 | return attrString 70 | } 71 | 72 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 73 | calendar.firstWeekday = row + 1 74 | calendarWeekBarView.calendar = calendar 75 | calendarView.calendar = calendar 76 | } 77 | } 78 | 79 | // MARK: - YMCalendarWeekBarDataSource 80 | extension BasicViewController: YMCalendarWeekBarAppearance { 81 | func horizontalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 82 | return .white 83 | } 84 | 85 | func verticalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 86 | return .white 87 | } 88 | 89 | // weekday: Int 90 | // e.g.) 1: Sunday, 2: Monday,.., 6: Friday, 7: Saturday 91 | 92 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, textAtWeekday weekday: Int) -> String { 93 | return symbols[weekday - 1] 94 | } 95 | 96 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, textColorAtWeekday weekday: Int) -> UIColor { 97 | switch weekday { 98 | case 1: // Sun 99 | return .deeppink 100 | case 7: // Sat 101 | return .turquoise 102 | default: 103 | return .white 104 | } 105 | } 106 | } 107 | 108 | // MARK: - YMCalendarDelegate 109 | extension BasicViewController: YMCalendarDelegate { 110 | func calendarView(_ view: YMCalendarView, didSelectDayCellAtDate date: Date) { 111 | let formatter = DateFormatter() 112 | formatter.dateFormat = "YYYY-MM-dd" 113 | navigationItem.title = formatter.string(from: date) 114 | } 115 | 116 | func calendarView(_ view: YMCalendarView, didMoveMonthOfStartDate date: Date) { 117 | // // If you want to auto select when displaying month has changed 118 | // view.selectDayCell(at: date) 119 | } 120 | } 121 | 122 | // MARK: - YMCalendarDataSource 123 | extension BasicViewController: YMCalendarDataSource { 124 | func calendarView(_ view: YMCalendarView, numberOfEventsAtDate date: Date) -> Int { 125 | if calendar.isDateInToday(date) 126 | || calendar.isDate(date, inSameDayAs: calendar.endOfMonthForDate(date)) 127 | || calendar.isDate(date, inSameDayAs: calendar.startOfMonthForDate(date)) { 128 | return 1 129 | } 130 | return 0 131 | } 132 | 133 | func calendarView(_ view: YMCalendarView, dateRangeForEventAtIndex index: Int, date: Date) -> DateRange? { 134 | if calendar.isDateInToday(date) 135 | || calendar.isDate(date, inSameDayAs: calendar.endOfMonthForDate(date)) 136 | || calendar.isDate(date, inSameDayAs: calendar.startOfMonthForDate(date)) { 137 | return DateRange(start: date, end: calendar.endOfDayForDate(date)) 138 | } 139 | return nil 140 | } 141 | 142 | func calendarView(_ view: YMCalendarView, eventViewForEventAtIndex index: Int, date: Date) -> YMEventView { 143 | guard let view = view.dequeueReusableCellWithIdentifier("YMEventStandardView", forEventAtIndex: index, date: date) as? YMEventStandardView else { 144 | fatalError() 145 | } 146 | if calendar.isDateInToday(date) { 147 | view.title = "today😃" 148 | } else if calendar.isDate(date, inSameDayAs: calendar.startOfMonthForDate(date)) { 149 | view.title = "startOfMonth" 150 | } else if calendar.isDate(date, inSameDayAs: calendar.endOfMonthForDate(date)) { 151 | view.title = "endOfMonth" 152 | } 153 | view.textColor = .white 154 | view.backgroundColor = .seagreen 155 | return view 156 | } 157 | } 158 | 159 | // MARK: - YMCalendarAppearance 160 | extension BasicViewController: YMCalendarAppearance { 161 | // grid lines 162 | 163 | func weekBarHorizontalGridColor(in view: YMCalendarView) -> UIColor { 164 | return .white 165 | } 166 | 167 | func weekBarVerticalGridColor(in view: YMCalendarView) -> UIColor { 168 | return .white 169 | } 170 | 171 | // dayLabel 172 | 173 | func dayLabelAlignment(in view: YMCalendarView) -> YMDayLabelAlignment { 174 | return .center 175 | } 176 | 177 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor { 178 | let weekday = calendar.component(.weekday, from: date) 179 | switch weekday { 180 | case 1: // Sun 181 | return .deeppink 182 | case 7: // Sat 183 | return .turquoise 184 | default: 185 | return .white 186 | } 187 | } 188 | 189 | // Selected dayLabel Color 190 | 191 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor { 192 | return .white 193 | } 194 | 195 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor { 196 | return .deeppink 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /YMCalendarDemo/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 5/6/17. 6 | // Copyright © 2017 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIColor { 13 | static var dark: UIColor { 14 | return UIColor(red: 42/255, green: 52/255, blue: 58/255, alpha: 1.0) 15 | } 16 | 17 | static var deeppink: UIColor { 18 | return UIColor(red: 253/255, green: 63/255, blue: 127/255, alpha: 1.0) 19 | } 20 | 21 | static var turquoise: UIColor { 22 | return UIColor(red: 0, green: 206/255, blue: 209/255, alpha: 1.0) 23 | } 24 | 25 | static var seagreen: UIColor { 26 | return UIColor(red: 67/255, green: 205/255, blue: 128/255, alpha: 1.0) 27 | } 28 | 29 | static var violetred: UIColor { 30 | return UIColor(red: 185/255, green: 29/255, blue: 81/255, alpha: 1.0) 31 | } 32 | 33 | static var sienna: UIColor { 34 | return UIColor(red: 204/255, green: 117/255, blue: 54/255, alpha: 1.0) 35 | } 36 | 37 | static var lightblue: UIColor { 38 | return UIColor(red: 69/255, green: 176/255, blue: 208/255, alpha: 1.0) 39 | } 40 | 41 | static var oceanblue: UIColor { 42 | return UIColor(red: 81/255, green: 110/255, blue: 190/255, alpha: 1.0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /YMCalendarDemo/EKEventKitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EKEventKitViewController.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 2017/04/02. 6 | // Copyright © 2017年 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import YMCalendar 11 | 12 | /** 13 | YMCalendarEKViewController is userful UIViewController superclass. 14 | It has YMCalendarView as `calendarView` as default. If you want to access 15 | and get events from calendar app instantly, only to implement this superclass. 16 | */ 17 | 18 | final class EKEventKitViewController: YMCalendarEKViewController, YMCalendarAppearance { 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | calendarView.appearance = self 24 | calendarView.scrollDirection = .vertical 25 | calendarView.gradientColors = [.lightblue, .oceanblue] 26 | } 27 | 28 | func calendarView(_ view: YMCalendarView, didSelectEventAtIndex index: Int, date: Date) { 29 | let event = eventAtIndex(index, date: date) 30 | print(event.title) 31 | } 32 | 33 | func horizontalGridColor(in view: YMCalendarView) -> UIColor { 34 | return .white 35 | } 36 | 37 | func verticalGridColor(in view: YMCalendarView) -> UIColor { 38 | return .white 39 | } 40 | 41 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor { 42 | return .white 43 | } 44 | 45 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor { 46 | return .oceanblue 47 | } 48 | 49 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor { 50 | return .white 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /YMCalendarDemo/GradientViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GradientViewController.swift 3 | // YMCalendarDemo 4 | // 5 | // Created by Yuma Matsune on 5/6/17. 6 | // Copyright © 2017 Yuma Matsune. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import YMCalendar 12 | 13 | final class GradientViewController: UIViewController { 14 | 15 | @IBOutlet weak var calendarWeekBarView: YMCalendarWeekBarView! 16 | @IBOutlet weak var calendarView: YMCalendarView! 17 | 18 | let calendar = Calendar.current 19 | 20 | let MyCustomEventViewIdentifier = "MyCustomEventViewIdentifier" 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | calendarWeekBarView.appearance = self 26 | calendarWeekBarView.calendar = calendar 27 | calendarWeekBarView.gradientColors = [.sienna, .violetred] 28 | calendarWeekBarView.gradientStartPoint = CGPoint(x: 0.0, y: 0.5) 29 | calendarWeekBarView.gradientEndPoint = CGPoint(x: 1.0, y: 0.5) 30 | calendarView.monthRange = MonthRange(start: MonthDate(year: 2017, month: 12), end: MonthDate(year: 2018, month: 3)) 31 | 32 | calendarView.appearance = self 33 | calendarView.delegate = self 34 | calendarView.dataSource = self 35 | calendarView.calendar = calendar 36 | calendarView.isPagingEnabled = true 37 | calendarView.scrollDirection = .horizontal 38 | calendarView.selectAnimation = .fade 39 | calendarView.eventViewHeight = 22 40 | calendarView.gradientColors = [.sienna, .violetred] 41 | calendarView.gradientStartPoint = CGPoint(x: 0.0, y: 0.5) 42 | calendarView.gradientEndPoint = CGPoint(x: 1.0, y: 0.5) 43 | } 44 | 45 | private func makeDate(year: Int, month: Int, day: Int) -> Date { 46 | let dateComponents = DateComponents(year: year, month: month, day: day) 47 | guard let date = Calendar.current.date(from: dateComponents) else { 48 | assertionFailure("can`t crate Date") 49 | return Date.distantPast 50 | } 51 | return date 52 | } 53 | } 54 | 55 | extension GradientViewController: YMCalendarWeekBarAppearance { 56 | func weekBarHorizontalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 57 | return .clear 58 | } 59 | 60 | func weekBarVerticalGridColor(in view: YMCalendarWeekBarView) -> UIColor { 61 | return .white 62 | } 63 | 64 | func calendarWeekBarView(_ view: YMCalendarWeekBarView, textColorAtWeekday weekday: Int) -> UIColor { 65 | return .white 66 | } 67 | } 68 | 69 | extension GradientViewController: YMCalendarAppearance { 70 | func horizontalGridColor(in view: YMCalendarView) -> UIColor { 71 | return .white 72 | } 73 | 74 | func verticalGridColor(in view: YMCalendarView) -> UIColor { 75 | return .clear 76 | } 77 | 78 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelTextColorAtDate date: Date) -> UIColor { 79 | return .white 80 | } 81 | 82 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedTextColorAtDate date: Date) -> UIColor { 83 | return .sienna 84 | } 85 | 86 | func calendarViewAppearance(_ view: YMCalendarView, dayLabelSelectedBackgroundColorAtDate date: Date) -> UIColor { 87 | return .white 88 | } 89 | } 90 | 91 | extension GradientViewController: YMCalendarDelegate { 92 | func calendarView(_ view: YMCalendarView, didSelectDayCellAtDate date: Date) { 93 | let formatter = DateFormatter() 94 | formatter.dateFormat = "YYYY-MM-dd" 95 | navigationItem.title = formatter.string(from: date) 96 | } 97 | } 98 | 99 | extension GradientViewController: YMCalendarDataSource { 100 | func calendarView(_ view: YMCalendarView, numberOfEventsAtDate date: Date) -> Int { 101 | if calendar.isDateInToday(date) { 102 | return 6 103 | } 104 | return 0 105 | } 106 | 107 | func calendarView(_ view: YMCalendarView, dateRangeForEventAtIndex index: Int, date: Date) -> DateRange? { 108 | if calendar.isDateInToday(date) { 109 | return DateRange(start: date, end: calendar.endOfDayForDate(date)) 110 | } 111 | return nil 112 | } 113 | 114 | func calendarView(_ view: YMCalendarView, eventViewForEventAtIndex index: Int, date: Date) -> YMEventView { 115 | return YMEventView() 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /YMCalendarDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSCalendarsUsageDescription 6 | Access Calendar 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | --------------------------------------------------------------------------------