├── .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 | 
2 | ---
3 |
4 | [](https://travis-ci.org/matsune/YMCalendar)
5 | [](https://swift.org/)
6 | 
7 | [](https://github.com/Carthage/Carthage)
8 | 
9 | [](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 | 
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 |
--------------------------------------------------------------------------------