├── Pod ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── MJWeekView.swift │ ├── MJComponentView.swift │ ├── MJWeekLabelsView.swift │ ├── MJConfiguration.swift │ ├── MJPeriodView.swift │ ├── MJDayView.swift │ └── MJCalendarView.swift ├── _Pods.xcodeproj ├── demo.gif ├── Package.swift ├── Screenshots ├── animation.gif ├── calendar_month.png ├── date_picker_may.png ├── calendar_one_week.png ├── date_picker_april.png ├── date_picker_march.png ├── calendar_three_weeks.png └── calendar_two_weeks.png ├── Example ├── MJCalendar.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── MJCalendar-Example.xcscheme │ └── project.pbxproj ├── MJCalendar.xcworkspace │ └── contents.xcworkspacedata ├── Podfile ├── Podfile.lock ├── MJCalendar │ ├── TableViewCell.swift │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── DatePickerViewController.swift │ └── ViewController.swift └── Tests │ └── Info.plist ├── .gitignore ├── .travis.yml ├── LICENSE ├── MJCalendar.podspec └── README.md /Pod/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Pod/Classes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/demo.gif -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | 4 | let package = Package( 5 | name: "MJCalendar" 6 | ) -------------------------------------------------------------------------------- /Screenshots/animation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/animation.gif -------------------------------------------------------------------------------- /Screenshots/calendar_month.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/calendar_month.png -------------------------------------------------------------------------------- /Screenshots/date_picker_may.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/date_picker_may.png -------------------------------------------------------------------------------- /Screenshots/calendar_one_week.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/calendar_one_week.png -------------------------------------------------------------------------------- /Screenshots/date_picker_april.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/date_picker_april.png -------------------------------------------------------------------------------- /Screenshots/date_picker_march.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/date_picker_march.png -------------------------------------------------------------------------------- /Screenshots/calendar_three_weeks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/calendar_three_weeks.png -------------------------------------------------------------------------------- /Screenshots/calendar_two_weeks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jackowski/MJCalendar/HEAD/Screenshots/calendar_two_weeks.png -------------------------------------------------------------------------------- /Example/MJCalendar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/MJCalendar.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | use_frameworks! 3 | inhibit_all_warnings! 4 | platform :ios, '8.0' 5 | 6 | target 'MJCalendar_Example' do 7 | pod "MJCalendar", :path => "../" 8 | pod 'HexColors', '~> 2.3' 9 | end 10 | 11 | target 'MJCalendar_Tests' do 12 | pod "MJCalendar", :path => "../" 13 | end 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | Example/build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | Example/Pods/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | language: objective-c 6 | # cache: cocoapods 7 | # podfile: Example/Podfile 8 | # before_install: 9 | # - gem install cocoapods # Since Travis is not always on latest version 10 | # - pod install --project-directory=Example 11 | install: 12 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 13 | script: 14 | - set -o pipefail && xcodebuild test -workspace Example/MJCalendar.xcworkspace -scheme MJCalendar-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty -c 15 | - pod lib lint --quick 16 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - HexColors (2.3.0) 3 | - MJCalendar (0.3.0): 4 | - NSDate-Escort (~> 1.5) 5 | - UIView+JMFrame 6 | - NSDate-Escort (1.7.0) 7 | - UIView+JMFrame (0.1.2) 8 | 9 | DEPENDENCIES: 10 | - HexColors (~> 2.3) 11 | - MJCalendar (from `../`) 12 | 13 | EXTERNAL SOURCES: 14 | MJCalendar: 15 | :path: ../ 16 | 17 | SPEC CHECKSUMS: 18 | HexColors: 6ad3947c3447a055a3aa8efa859def096351fe5f 19 | MJCalendar: fded170474124dc14ae81868f7ffc69bc5438b6f 20 | NSDate-Escort: 3469fefab423c094108c2e864416b61b6ac0edbc 21 | UIView+JMFrame: a907974c9d34b1c300aef85ae2a1a5af746f2b8a 22 | 23 | PODFILE CHECKSUM: 4a8b5d26398ddbd90a4263c73faf04b15b5e7d20 24 | 25 | COCOAPODS: 1.1.1 26 | -------------------------------------------------------------------------------- /Example/MJCalendar/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCell.swift 3 | // MJCalendar 4 | // 5 | // Created by Michał Jackowski on 04.11.2015. 6 | // Copyright (c) 2015 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TableViewCell: UITableViewCell { 12 | @IBOutlet weak var colorView: UIView! 13 | @IBOutlet weak var dateLabel: UILabel! 14 | 15 | override func awakeFromNib() { 16 | super.awakeFromNib() 17 | // Initialization code 18 | } 19 | 20 | override func setSelected(_ selected: Bool, animated: Bool) { 21 | super.setSelected(selected, animated: animated) 22 | 23 | // Configure the view for the selected state 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Example/MJCalendar/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/Tests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Michał Jackowski 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Example/MJCalendar/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 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 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /MJCalendar.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint MJCalendar.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = "MJCalendar" 11 | s.version = "0.4.0" 12 | s.summary = "Very customizable calendar component. Supports month, 3 week, 2 week and 1 week view." 13 | 14 | # This description is used to generate tags and improve search results. 15 | # * Think: What does it do? Why did you write it? What is the focus? 16 | # * Try to keep it short, snappy and to the point. 17 | # * Write the description between the DESC delimiters below. 18 | # * Finally, don't worry about the indent, CocoaPods strips it! 19 | 20 | s.homepage = "https://github.com/jackowski/MJCalendar" 21 | # s.screenshots = "www.example.com/screenshots_1", "www.example.com/screenshots_2" 22 | s.license = 'MIT' 23 | s.author = { "Michał Jackowski" => "mk.jackowski@gmail.com" } 24 | s.source = { :git => "https://github.com/jackowski/MJCalendar.git", :tag => s.version } 25 | # s.social_media_url = 'https://twitter.com/' 26 | 27 | s.platform = :ios, '8.0' 28 | s.requires_arc = true 29 | 30 | s.source_files = 'Pod/Classes/**/*' 31 | 32 | # s.public_header_files = 'Pod/Classes/**/*.h' 33 | s.frameworks = 'UIKit' 34 | s.dependency 'NSDate-Escort', '~> 1.5' 35 | s.dependency 'UIView+JMFrame' 36 | end 37 | -------------------------------------------------------------------------------- /Pod/Classes/MJWeekView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeekView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 21.09.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | 12 | open class MJWeekView: MJComponentView { 13 | var date: Date! { 14 | didSet { 15 | self.configureViews() 16 | } 17 | } 18 | var days: [MJDayView]? 19 | 20 | required public init?(coder aDecoder: NSCoder) { 21 | super.init(coder: aDecoder) 22 | } 23 | 24 | init(date: Date, delegate: MJComponentDelegate) { 25 | self.date = date 26 | super.init(delegate: delegate) 27 | self.configureViews() 28 | } 29 | 30 | func configureViews() { 31 | if let dayViews = self.days { 32 | for i in 1...7 { 33 | let dayDate = (self.date!.self as NSDate).addingDays(i-1) 34 | dayViews[i - 1].date = dayDate 35 | } 36 | } else { 37 | self.days = [] 38 | for i in 1...7 { 39 | let dayDate = (self.date!.self as NSDate).addingDays(i-1) 40 | let dayView = MJDayView(date: dayDate, delegate: self.delegate!) 41 | self.addSubview(dayView) 42 | self.days!.append(dayView) 43 | } 44 | } 45 | } 46 | 47 | override func updateFrame() { 48 | for (index, day) in (self.days!).enumerated() { 49 | let dayWidth = self.width() / 7 50 | day.frame = CGRect(x: CGFloat(index) * dayWidth, y: 0, width: dayWidth, height: self.height()) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pod/Classes/MJComponentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MJComponentView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 17.12.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | protocol MJComponentDelegate: NSObjectProtocol { 12 | func configurationWithComponent(_ componentView: MJComponentView) -> MJConfiguration 13 | func componentView(_ componentView: MJComponentView, isDateSelected date: Date) -> Bool 14 | func componentView(_ componentView: MJComponentView, didSelectDate date: Date) 15 | func isBeingAnimatedWithComponentView(_ componentView: MJComponentView) -> Bool 16 | func componentView(_ componentView: MJComponentView, backgroundColorForDate date: Date) -> UIColor? 17 | func componentView(_ componentView: MJComponentView, textColorForDate date: Date) -> UIColor? 18 | func isDateOutOfRange(_ componentView: MJComponentView, date: Date) -> Bool 19 | } 20 | 21 | open class MJComponentView: UIView { 22 | weak var delegate: MJComponentDelegate! 23 | var currentFrame = CGRect.zero 24 | 25 | init(delegate: MJComponentDelegate) { 26 | self.delegate = delegate 27 | super.init(frame: CGRect.zero) 28 | } 29 | 30 | required public init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | override open func layoutSubviews() { 35 | if !self.currentFrame.equalTo(self.frame) { 36 | self.currentFrame = self.frame 37 | self.updateFrame() 38 | } 39 | } 40 | 41 | func updateFrame() { 42 | fatalError("updateFrame has not been implemented") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Example/MJCalendar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MJCalendar 4 | // 5 | // Created by Michał Jackowski on 09/14/2015. 6 | // Copyright (c) 2015 Michał Jackowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // 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. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Pod/Classes/MJWeekLabelsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MJWeekLabelsView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 23.11.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | 11 | class MJWeekLabelsView: MJComponentView { 12 | var weekLabels: [UILabel] = [] 13 | 14 | lazy var formatter = DateFormatter() 15 | var dayWeekText:[String] { 16 | var dayWeekText:[String] = [] 17 | var dayIndex: Int 18 | 19 | for index in 1...7 { 20 | 21 | switch self.delegate.configurationWithComponent(self).startDayType { 22 | case .monday: 23 | dayIndex = index % 7 24 | case .sunday: 25 | dayIndex = index - 1 26 | } 27 | 28 | let day : NSString = formatter.weekdaySymbols[dayIndex] as NSString 29 | dayWeekText.append(day.substring(to: delegate.configurationWithComponent(self).lettersInWeekDayLabel.rawValue).uppercased()) 30 | } 31 | 32 | return dayWeekText 33 | } 34 | 35 | override init(delegate: MJComponentDelegate) { 36 | super.init(delegate: delegate) 37 | self.setUpView() 38 | } 39 | 40 | required init?(coder aDecoder: NSCoder) { 41 | super.init(coder: aDecoder) 42 | } 43 | 44 | func setUpView() { 45 | for i: Int in 0...6 { 46 | let label = UILabel() 47 | label.font = self.delegate.configurationWithComponent(self).weekLabelFont 48 | label.textColor = self.delegate.configurationWithComponent(self).weekLabelTextColor 49 | label.text = self.dayWeekText[i] 50 | label.textAlignment = .center 51 | self.addSubview(label) 52 | self.weekLabels.append(label) 53 | } 54 | } 55 | 56 | func updateView() { 57 | for (index, weekLabel) in self.weekLabels.enumerated() { 58 | weekLabel.font = self.delegate.configurationWithComponent(self).weekLabelFont 59 | weekLabel.textColor = self.delegate.configurationWithComponent(self).weekLabelTextColor 60 | weekLabel.text = self.dayWeekText[index] 61 | } 62 | } 63 | 64 | override func updateFrame() { 65 | for (index, weekLabel) in self.weekLabels.enumerated() { 66 | let labelWidth: CGFloat = self.width() / 7 67 | weekLabel.frame = CGRect(x: CGFloat(index) * labelWidth, y: 0, width: labelWidth, height: self.height()) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Pod/Classes/MJConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MJConfiguration.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 18.09.2015. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | public struct MJConfiguration { 12 | public enum PeriodType { 13 | case oneWeek, twoWeeks, threeWeeks, month 14 | func weeksCount() -> Int { 15 | switch self { 16 | case .month: return 6 17 | case .threeWeeks: return 3 18 | case .twoWeeks: return 2 19 | case .oneWeek: return 1 20 | } 21 | } 22 | } 23 | 24 | public enum DayViewType { 25 | case square, circle 26 | } 27 | 28 | public enum StartDayType { 29 | case monday, sunday 30 | } 31 | 32 | public enum LettersInWeekDay: Int { 33 | case one = 1 34 | case two 35 | case three 36 | } 37 | 38 | public enum SelectedDayType { 39 | case filled, border 40 | } 41 | 42 | public var lettersInWeekDayLabel:LettersInWeekDay = .three 43 | 44 | public var periodType: PeriodType = .month 45 | public var dayViewType: DayViewType = .circle 46 | public var startDayType: StartDayType = .monday 47 | public var selectedDayType: SelectedDayType = .border 48 | 49 | public var rowHeight: CGFloat = 30 50 | public var dayViewSize: CGSize = CGSize(width: 24, height: 24) 51 | public var dayTextFont = UIFont.systemFont(ofSize: 12) 52 | 53 | public var otherMonthBackgroundColor = UIColor.clear 54 | public var otherMonthDayViewBackgroundColor = UIColor.clear 55 | public var otherMonthTextColor = UIColor.clear 56 | 57 | public var dayBackgroundColor = UIColor.clear 58 | public var dayDayViewBackgroundColor = UIColor.clear 59 | public var dayTextColor = UIColor.clear 60 | 61 | public var selectedDayBackgroundColor = UIColor.clear 62 | public var selectedDayTextColor = UIColor.clear 63 | public var selectedBorderWidth: CGFloat = 1 64 | 65 | public var weekLabelFont = UIFont.systemFont(ofSize: 12) 66 | public var weekLabelTextColor = UIColor.clear 67 | public var weekLabelHeight: CGFloat = 25 68 | 69 | public var minDate: Date? 70 | public var maxDate: Date? 71 | 72 | public var outOfRangeDayBackgroundColor = UIColor.clear 73 | public var outOfRangeDayTextColor = UIColor.clear 74 | 75 | public var selectDayOnPeriodChange: Bool = true 76 | 77 | static func getDefault() -> MJConfiguration { 78 | let configuration = MJConfiguration() 79 | return configuration 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Example/MJCalendar/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Pod/Classes/MJPeriodView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeriodView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 21.09.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | 12 | open class MJPeriodView: MJComponentView { 13 | var date: Date! { 14 | didSet { 15 | self.configureViews() 16 | } 17 | } 18 | var weeks: [MJWeekView]? 19 | var numberOfWeeks: Int { 20 | return self.delegate!.configurationWithComponent(self).periodType.weeksCount() 21 | } 22 | 23 | required public init?(coder aDecoder: NSCoder) { 24 | super.init(coder: aDecoder) 25 | } 26 | 27 | init(date: Date, delegate: MJComponentDelegate) { 28 | self.date = date 29 | super.init(delegate: delegate) 30 | self.clipsToBounds = true 31 | self.configureViews() 32 | } 33 | 34 | func configureViews() { 35 | if let weekViews = self.weeks { 36 | for i in 1...self.numberOfWeeks { 37 | let weekDate = (self.date!.self as NSDate).addingDays((i-1) * 7) 38 | weekViews[i - 1].date = weekDate 39 | } 40 | } else { 41 | self.weeks = [] 42 | for i in 1...self.numberOfWeeks { 43 | let weekDate = (self.date!.self as NSDate).addingDays((i-1) * 7) 44 | let weekView = MJWeekView(date: weekDate, delegate: self.delegate!) 45 | self.addSubview(weekView) 46 | self.weeks!.append(weekView) 47 | } 48 | } 49 | 50 | self.setIsSameMonth() 51 | } 52 | 53 | func setIsSameMonth() { 54 | if (self.delegate.configurationWithComponent(self).periodType == .month) { 55 | let monthDate = self.weeks![1].date 56 | for (index, week) in (self.weeks!).enumerated() { 57 | if index == 0 || index == 4 || index == 5 { 58 | for dayView in week.days! { 59 | dayView.isSameMonth = (dayView.date as NSDate).atStartOfMonth() == (monthDate as? NSDate)?.atStartOfMonth() 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | override func updateFrame() { 67 | for (index, week) in (self.weeks!).enumerated() { 68 | let lineHeight = self.delegate.configurationWithComponent(self).rowHeight 69 | week.frame = CGRect(x: 0, y: CGFloat(index) * lineHeight, width: self.width(), height: lineHeight) 70 | } 71 | } 72 | 73 | open func startingDate() -> Date { 74 | return self.weeks!.first!.days!.first!.date! as Date 75 | } 76 | 77 | open func endingDate() -> Date { 78 | return self.weeks!.last!.days!.last!.date! as Date 79 | } 80 | 81 | open func startingPeriodDate() -> Date { 82 | let monthCount = MJConfiguration.PeriodType.month.weeksCount() 83 | if self.weeks?.count == monthCount { 84 | let middleDate = self.weeks![3].date 85 | return (middleDate! as NSDate).atStartOfMonth() 86 | } else { 87 | return startingDate() 88 | } 89 | } 90 | 91 | open func endingPeriodDate() -> Date { 92 | let monthCount = MJConfiguration.PeriodType.month.weeksCount() 93 | if self.weeks?.count == monthCount { 94 | let middleDate = self.weeks![3].date 95 | return (middleDate! as NSDate).atEndOfMonth() 96 | } else { 97 | return endingDate() 98 | } 99 | } 100 | 101 | open func isDateInPeriod(_ date: Date) -> Bool { 102 | return (date as NSDate).isLaterThanOrEqualDate(startingPeriodDate()) && (date as NSDate).isEarlierThanOrEqualDate(endingPeriodDate()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Example/MJCalendar.xcodeproj/xcshareddata/xcschemes/MJCalendar-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/MJCalendar/DatePickerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatePickerViewController.swift 3 | // MJCalendar 4 | // 5 | // Created by Michał Jackowski on 25.03.2016. 6 | // Copyright © 2016 CocoaPods. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | import MJCalendar 12 | import HexColors 13 | 14 | class DatePickerViewController: UIViewController, MJCalendarViewDelegate { 15 | @IBOutlet weak var calendarView: MJCalendarView! 16 | @IBOutlet weak var calendarViewHeight: NSLayoutConstraint! 17 | @IBOutlet weak var monthNameLabel: UILabel! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | 22 | self.setUpCalendarConfiguration() 23 | self.setTitleWithDate(Date()) 24 | } 25 | 26 | func setUpCalendarConfiguration() { 27 | self.calendarView.calendarDelegate = self 28 | 29 | // Set displayed period type. Available types: Month, ThreeWeeks, TwoWeeks, OneWeek 30 | self.calendarView.configuration.periodType = .month 31 | 32 | // Set shape of day view. Available types: Circle, Square 33 | self.calendarView.configuration.dayViewType = .circle 34 | 35 | // Set selected day display type. Available types: 36 | // Border - Only border is colored with selected day color 37 | // Filled - Entire day view is filled with selected day color 38 | self.calendarView.configuration.selectedDayType = .filled 39 | 40 | // Set day text color 41 | self.calendarView.configuration.dayTextColor = UIColor(hexString: "6f787c") 42 | 43 | // Set day background color 44 | self.calendarView.configuration.dayBackgroundColor = UIColor(hexString: "f0f0f0") 45 | 46 | // Set selected day text color 47 | self.calendarView.configuration.selectedDayTextColor = UIColor.white 48 | 49 | // Set selected day background color 50 | self.calendarView.configuration.selectedDayBackgroundColor = UIColor(hexString: "2FBD8F") 51 | 52 | // Set other month day text color. Relevant only if periodType = .Month 53 | self.calendarView.configuration.otherMonthTextColor = UIColor(hexString: "6f787c") 54 | 55 | // Set other month background color. Relevant only if periodType = .Month 56 | self.calendarView.configuration.otherMonthBackgroundColor = UIColor(hexString: "E7E7E7") 57 | 58 | // Set week text color 59 | self.calendarView.configuration.weekLabelTextColor = UIColor(hexString: "6f787c") 60 | 61 | // Set start day. Available type: .Monday, Sunday 62 | self.calendarView.configuration.startDayType = .monday 63 | 64 | // Set day text font 65 | self.calendarView.configuration.dayTextFont = UIFont.systemFont(ofSize: 12) 66 | 67 | //Set week's day name font 68 | self.calendarView.configuration.weekLabelFont = UIFont.systemFont(ofSize: 12) 69 | 70 | //Set day view size. It includes border width if selectedDayType = .Border 71 | self.calendarView.configuration.dayViewSize = CGSize(width: 24, height: 24) 72 | 73 | //Set height of row with week's days 74 | self.calendarView.configuration.rowHeight = 30 75 | 76 | // Set height of week's days names view 77 | self.calendarView.configuration.weekLabelHeight = 25 78 | 79 | 80 | //Set min date 81 | self.calendarView.configuration.minDate = (Date() as NSDate).subtractingDays(60) 82 | 83 | //Set max date 84 | self.calendarView.configuration.maxDate = (Date() as NSDate).addingDays(60) 85 | 86 | self.calendarView.configuration.outOfRangeDayBackgroundColor = UIColor(hexString: "E7E7E7") 87 | self.calendarView.configuration.outOfRangeDayTextColor = UIColor(hexString: "6f787c") 88 | 89 | self.calendarView.configuration.selectDayOnPeriodChange = false 90 | 91 | // To commit all configuration changes execute reloadView method 92 | self.calendarView.reloadView() 93 | } 94 | 95 | func setTitleWithDate(_ date: Date) { 96 | let dateFormatter = DateFormatter() 97 | dateFormatter.dateFormat = "MMMM yy" 98 | self.monthNameLabel.text = dateFormatter.string(from: date) 99 | } 100 | 101 | //MARK: MJCalendarViewDelegate 102 | func calendar(_ calendarView: MJCalendarView, didChangePeriod periodDate: Date, bySwipe: Bool) { 103 | // Sets month name according to presented dates 104 | self.setTitleWithDate(periodDate) 105 | } 106 | 107 | func calendar(_ calendarView: MJCalendarView, backgroundForDate date: Date) -> UIColor? { 108 | return nil 109 | } 110 | 111 | func calendar(_ calendarView: MJCalendarView, textColorForDate date: Date) -> UIColor? { 112 | return nil 113 | } 114 | 115 | func calendar(_ calendarView: MJCalendarView, didSelectDate date: Date) { 116 | 117 | } 118 | 119 | //MARK: Toolbar actions 120 | @IBAction func didTapMonth(_ sender: AnyObject) { 121 | self.animateToPeriod(.month) 122 | } 123 | 124 | @IBAction func didTapThreeWeeks(_ sender: AnyObject) { 125 | self.animateToPeriod(.threeWeeks) 126 | } 127 | 128 | @IBAction func didTapTwoWeeks(_ sender: AnyObject) { 129 | self.animateToPeriod(.twoWeeks) 130 | } 131 | 132 | @IBAction func didTapOneWeek(_ sender: AnyObject) { 133 | self.animateToPeriod(.oneWeek) 134 | } 135 | 136 | func animateToPeriod(_ period: MJConfiguration.PeriodType) { 137 | self.calendarView.animateToPeriodType(period, duration: 0.2, animations: { (calendarHeight) -> Void in 138 | // In animation block you can add your own animation. To adapat UI to new calendar height you can use calendarHeight param 139 | self.calendarViewHeight.constant = calendarHeight 140 | self.view.layoutIfNeeded() 141 | }, completion: nil) 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MJCalendar 2 | 3 | [![CI Status](http://img.shields.io/travis/Michał Jackowski/MJCalendar.svg?style=flat)](https://travis-ci.org/Michał Jackowski/MJCalendar) 4 | [![Version](https://img.shields.io/cocoapods/v/MJCalendar.svg?style=flat)](http://cocoapods.org/pods/MJCalendar) 5 | [![License](https://img.shields.io/cocoapods/l/MJCalendar.svg?style=flat)](http://cocoapods.org/pods/MJCalendar) 6 | [![Platform](https://img.shields.io/cocoapods/p/MJCalendar.svg?style=flat)](http://cocoapods.org/pods/MJCalendar) 7 | 8 | ## Usage 9 | 10 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 11 | 12 | ## Installation 13 | 14 | MJCalendar is available through [CocoaPods](http://cocoapods.org). To install 15 | it, simply add the following line to your Podfile: 16 | 17 | ```ruby 18 | pod "MJCalendar" 19 | ``` 20 | 21 | ##Calendar View## 22 | 23 | 24 | 25 | 26 | 27 | ## Customization ## 28 | 29 | `MJConfiguration` class is responsible for all customizable attributes. To commit all configuration changes use method `reloadView()`. 30 | 31 | ```swift 32 | // Set displayed period type. Available types: Month, ThreeWeeks, TwoWeeks, OneWeek 33 | self.calendarView.configuration.periodType = .Month 34 | 35 | // Set shape of day view. Available types: Circle, Square 36 | self.calendarView.configuration.dayViewType = .Circle 37 | 38 | self.calendarView.reloadView() 39 | ``` 40 | 41 | ## Configuration 42 | 43 | | Key | Type | Default value | 44 | |:-------------------------------------------------:|:---------------------------:|:----------------------------:| 45 | | periodType | enum [.Month, .ThreeWeeks, .TwoWeeks, .OneWeek] | .Month | 46 | | dayViewType | enum [.Square, .Circle] | .Circle | 47 | | startDayType | enum [.Monday, .Sunday] | .Monday | 48 | | selectedDayType | enum [.Filled, .Border] | .Border | 49 | | rowHeight | CGFloat | 30 | 50 | | dayViewSize | CGSize | CGSizeMake(24,24) | 51 | | dayTextFont | UIFont | UIFont.systemFontOfSize(12) | 52 | | otherMonthBackgroundColor | UIColor | UIColor.clearColor() | 53 | | otherMonthDayViewBackgroundColor | UIColor | UIColor.clearColor() | 54 | | otherMonthTextColor | UIColor | UIColor.clearColor() | 55 | | dayBackgroundColor | UIColor | UIColor.clearColor() | 56 | | dayDayViewBackgroundColor | UIColor | UIColor.clearColor() | 57 | | dayTextColor | UIColor | UIColor.clearColor() | 58 | | selectedDayBackgroundColor | UIColor | UIColor.clearColor() | 59 | | selectedDayTextColor | UIColor | UIColor.clearColor() | 60 | | selectedBorderWidth | CGFloat | 1 | 61 | | weekLabelFont | UIFont | UIFont.systemFontOfSize(12) | 62 | | weekLabelTextColor | UIColor | UIColor.clearColor() | 63 | | weekLabelHeight | CGFloat | 25 | 64 | | minDate | NSDate? | nil | 65 | | maxDate | NSDate? | nil | 66 | | outOfRangeDayBackgroundColor | UIColor | UIColor.clearColor() | 67 | | outOfRangeDayTextColor | UIColor | UIColor.clearColor() | 68 | | selectDayOnPeriodChange | Bool | true | 69 | 70 | 71 | 72 | ## Delegates 73 | 74 | Apart from setting colors in calendar configuration, it might be set in delegates methods. 75 | Returning nil means that the color from configuration will be displayed. 76 | 77 | 78 | ```swift 79 | func calendar(calendarView: MJCalendarView, textColorForDate date: NSDate) -> UIColor? { 80 | return self.dayColors[date]?.textColor 81 | } 82 | 83 | func calendar(calendarView: MJCalendarView, backgroundForDate date: NSDate) -> UIColor? { 84 | return self.dayColors[date]?.backgroundColor 85 | } 86 | ``` 87 | 88 | Once displayed period of time is changed either by left right swipe or by select date method, method didChangePeriod is triggered. Usage example. 89 | 90 | 91 | ```swift 92 | func calendar(calendarView: MJCalendarView, didChangePeriod periodDate: NSDate, bySwipe: Bool) { 93 | // Sets month name according to presented dates 94 | self.setTitleWithDate(periodDate) 95 | 96 | // bySwipe diffrentiate changes made from swipes or select date method 97 | if bySwipe { 98 | // Scroll to relevant date in tableview 99 | self.scrollTableViewToDate(periodDate) 100 | } 101 | } 102 | ``` 103 | 104 | When user selects date by tapping on particular day, method didSelectDate is triggered. Usage example. 105 | 106 | 107 | ```swift 108 | func calendar(calendarView: MJCalendarView, didSelectDate date: NSDate) { 109 | self.scrollTableViewToDate(date) 110 | } 111 | 112 | ``` 113 | 114 | ## Date Range 115 | Variables `minDate` and `maxDate` from `MJConfiguration` are responsible for setting range of available dates. To define UI use properties: `outOfRangeDayBackgroundColor`, `outOfRangeDayTextColor`. 116 | 117 | 118 | 119 | 120 | 121 | ## Public methods 122 | 123 | To select date from code use method `selectDate`. Usage example. 124 | 125 | 126 | ```swift 127 | func scrollViewDidScroll(scrollView: UIScrollView) { 128 | // Prevent changing selected day when non user scroll is triggered. 129 | if !self.isScrollingAnimation { 130 | // Get all visible cells from tableview 131 | if let visibleCells = self.tableView.indexPathsForVisibleRows { 132 | if let cellIndexPath = visibleCells.first { 133 | // Get day by indexPath 134 | let day = self.dateByIndex(cellIndexPath.row) 135 | 136 | //Select day according to first visible cell in tableview 137 | self.calendarView.selectDate(day) 138 | } 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | ## Period change animations 145 | 146 | To change displayed period on runtime use method animateToPeriod. 147 | Method params: 148 | 149 | - periodType: available types: `Month`, `ThreeWeeks`, `TwoWeeks`, `OneWeek`. If type is same as one already displayed no animation is being performed. 150 | - duration: duration of animation in seconds 151 | - animation block: optional block to add custom animation while period is being animated 152 | Usage example. 153 | 154 | ```swift 155 | self.calendarView.animateToPeriodType(period, duration: 0.2, animations: { (calendarHeight) -> Void in 156 | // In animation block you can add your own animation. To adapat UI to new calendar height you can use calendarHeight param 157 | self.calendarViewHeight.constant = calendarHeight 158 | self.view.layoutIfNeeded() 159 | }, completion: nil) 160 | ``` 161 | 162 | 163 | ## Requirements 164 | 165 | Xcode 7+, Swift 2.0+ 166 | 167 | 168 | ## Author 169 | 170 | Michał Jackowski, mk.jackowski@gmail.com 171 | 172 | ## License 173 | 174 | MJCalendar is available under the MIT license. See the LICENSE file for more info. 175 | -------------------------------------------------------------------------------- /Pod/Classes/MJDayView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DayView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 21.09.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | 12 | open class MJDayView: MJComponentView { 13 | var date: Date! { 14 | didSet { 15 | self.updateView() 16 | } 17 | } 18 | var todayDate: Date! 19 | var label: UILabel! 20 | var borderView: UIView! 21 | var isSameMonth = true { 22 | didSet { 23 | if isSameMonth != oldValue { 24 | self.updateView() 25 | } 26 | } 27 | } 28 | 29 | required public init?(coder aDecoder: NSCoder) { 30 | super.init(coder: aDecoder) 31 | } 32 | 33 | init(date: Date, delegate: MJComponentDelegate) { 34 | self.date = date 35 | self.todayDate = (Date() as NSDate).atStartOfDay() 36 | super.init(delegate: delegate) 37 | self.setUpGesture() 38 | self.setUpBorderView() 39 | self.setUpLabel() 40 | self.updateView() 41 | } 42 | 43 | func setUpGesture() { 44 | let tap = UITapGestureRecognizer(target: self, action: #selector(MJDayView.didTap)) 45 | self.addGestureRecognizer(tap) 46 | } 47 | 48 | func didTap() { 49 | if !self.delegate.isDateOutOfRange(self, date: self.date) { 50 | self.delegate.componentView(self, didSelectDate: self.date) 51 | } 52 | } 53 | 54 | func setUpBorderView() { 55 | self.borderView = UIView() 56 | self.addSubview(self.borderView) 57 | } 58 | 59 | func setUpLabel() { 60 | self.label = UILabel() 61 | self.label.textAlignment = .center 62 | self.label.clipsToBounds = true 63 | self.addSubview(self.label) 64 | } 65 | 66 | override func updateFrame() { 67 | let labelSize = self.labelSize() 68 | let labelFrame = CGRect(x: (self.width() - labelSize.width) / 2, 69 | y: (self.height() - labelSize.height) / 2, width: labelSize.width, height: labelSize.height) 70 | self.label.frame = labelFrame 71 | 72 | let dayViewSize = self.delegate.configurationWithComponent(self).dayViewSize 73 | let borderFrame = CGRect(x: (self.width() - dayViewSize.width) / 2, 74 | y: (self.height() - dayViewSize.height) / 2, width: dayViewSize.width, height: dayViewSize.height) 75 | self.borderView.frame = borderFrame 76 | } 77 | 78 | func labelSize() -> CGSize { 79 | let dayViewSize = self.delegate.configurationWithComponent(self).dayViewSize 80 | let borderSize = self.delegate.configurationWithComponent(self).selectedBorderWidth 81 | let labelSize = self.delegate.configurationWithComponent(self).selectedDayType == .filled 82 | ? dayViewSize 83 | : CGSize(width: dayViewSize.width - 2 * borderSize, height: dayViewSize.height - 2 * borderSize) 84 | return labelSize 85 | } 86 | 87 | func updateView() { 88 | self.setText() 89 | self.setShape() 90 | self.setBackgrounds() 91 | self.setTextColors() 92 | self.setViewBackgrounds() 93 | self.setBorder() 94 | } 95 | 96 | func setText() { 97 | self.label.font = self.delegate.configurationWithComponent(self).dayTextFont 98 | let text = "\((self.date as NSDate).day)" 99 | self.label.text = text 100 | 101 | let isToday = self.todayDate.timeIntervalSince1970 == self.date.timeIntervalSince1970 102 | if isToday { 103 | let underlineAttribute = [NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue] 104 | self.label.attributedText = NSAttributedString(string: text, attributes: underlineAttribute) 105 | } else { 106 | self.label.attributedText = NSAttributedString(string: text) 107 | } 108 | } 109 | 110 | func setShape() { 111 | let labelCornerRadius = self.delegate.configurationWithComponent(self).dayViewType == .circle 112 | ? self.labelSize().width / 2 113 | : 0 114 | self.label.layer.cornerRadius = labelCornerRadius 115 | let borderCornerRadius = self.delegate.configurationWithComponent(self).dayViewSize.width / 2 116 | self.borderView.layer.cornerRadius = borderCornerRadius 117 | } 118 | 119 | func setViewBackgrounds() { 120 | if self.isSameMonth { 121 | if self.delegate.isDateOutOfRange(self, date: self.date) { 122 | self.backgroundColor = self.delegate.configurationWithComponent(self).outOfRangeDayBackgroundColor 123 | } else { 124 | self.backgroundColor = self.delegate.configurationWithComponent(self).dayBackgroundColor 125 | } 126 | } else { 127 | self.backgroundColor = self.delegate.configurationWithComponent(self).otherMonthBackgroundColor 128 | } 129 | } 130 | 131 | func setTextColors() { 132 | if self.delegate.componentView(self, isDateSelected: self.date) 133 | && self.delegate.configurationWithComponent(self).selectedDayType == .filled { 134 | self.label.textColor = self.delegate.configurationWithComponent(self).selectedDayTextColor 135 | } else if self.isSameMonth { 136 | if let textColor = self.delegate.componentView(self, textColorForDate: self.date) { 137 | self.label.textColor = textColor 138 | } else { 139 | if self.delegate.isDateOutOfRange(self, date: self.date) { 140 | self.label.textColor = self.delegate.configurationWithComponent(self).outOfRangeDayTextColor 141 | } else { 142 | self.label.textColor = self.delegate.configurationWithComponent(self).dayTextColor 143 | } 144 | } 145 | } else { 146 | self.label.textColor = self.delegate.configurationWithComponent(self).otherMonthTextColor 147 | } 148 | } 149 | 150 | func setBackgrounds() { 151 | if self.delegate.componentView(self, isDateSelected: self.date) 152 | && self.delegate.configurationWithComponent(self).selectedDayType == .filled { 153 | self.label.backgroundColor = self.delegate.configurationWithComponent(self).selectedDayBackgroundColor 154 | } else if self.isSameMonth { 155 | if let backgroundColor = self.delegate.componentView(self, backgroundColorForDate: self.date) { 156 | self.label.backgroundColor = backgroundColor 157 | } else { 158 | if self.delegate.isDateOutOfRange(self, date: self.date) { 159 | self.label.backgroundColor = self.delegate.configurationWithComponent(self).outOfRangeDayBackgroundColor 160 | } else { 161 | self.label.backgroundColor = self.delegate.configurationWithComponent(self).dayBackgroundColor 162 | } 163 | } 164 | } else { 165 | self.label.backgroundColor = self.delegate.configurationWithComponent(self).otherMonthBackgroundColor 166 | } 167 | } 168 | 169 | func setBorder() { 170 | self.borderView.backgroundColor = self.delegate.configurationWithComponent(self).selectedDayBackgroundColor 171 | self.borderView.isHidden = !(self.delegate.componentView(self, isDateSelected: self.date) && isSameMonth) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Example/MJCalendar/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MJCalendar 4 | // 5 | // Created by Michał Jackowski on 09/14/2015. 6 | // Copyright (c) 2015 Michał Jackowski. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | import MJCalendar 12 | import HexColors 13 | 14 | struct DayColors { 15 | var backgroundColor: UIColor 16 | var textColor: UIColor 17 | } 18 | 19 | class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate, MJCalendarViewDelegate { 20 | 21 | @IBOutlet weak var calendarView: MJCalendarView! 22 | @IBOutlet weak var tableView: UITableView! 23 | @IBOutlet weak var calendarViewHeight: NSLayoutConstraint! 24 | 25 | var dayColors = Dictionary() 26 | var dateFormatter: DateFormatter! 27 | var colors: [UIColor] { 28 | return [ 29 | UIColor(hexString: "#f6980b"), 30 | UIColor(hexString: "#2081D9"), 31 | UIColor(hexString: "#2fbd8f"), 32 | ] 33 | } 34 | 35 | let daysRange = 365 36 | var isScrollingAnimation = false 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | self.setUpDays() 41 | 42 | self.setUpCalendarConfiguration() 43 | 44 | self.dateFormatter = DateFormatter() 45 | self.setTitleWithDate(Date()) 46 | } 47 | 48 | func setUpCalendarConfiguration() { 49 | self.calendarView.calendarDelegate = self 50 | 51 | // Set displayed period type. Available types: Month, ThreeWeeks, TwoWeeks, OneWeek 52 | self.calendarView.configuration.periodType = .month 53 | 54 | // Set shape of day view. Available types: Circle, Square 55 | self.calendarView.configuration.dayViewType = .circle 56 | 57 | // Set selected day display type. Available types: 58 | // Border - Only border is colored with selected day color 59 | // Filled - Entire day view is filled with selected day color 60 | self.calendarView.configuration.selectedDayType = .border 61 | 62 | // Set width of selected day border. Relevant only if selectedDayType = .Border 63 | self.calendarView.configuration.selectedBorderWidth = 1 64 | 65 | // Set day text color 66 | self.calendarView.configuration.dayTextColor = UIColor(hexString: "6f787c") 67 | 68 | // Set day background color 69 | self.calendarView.configuration.dayBackgroundColor = UIColor(hexString: "f0f0f0") 70 | 71 | // Set selected day text color 72 | self.calendarView.configuration.selectedDayTextColor = UIColor.white 73 | 74 | // Set selected day background color 75 | self.calendarView.configuration.selectedDayBackgroundColor = UIColor(hexString: "6f787c") 76 | 77 | // Set other month day text color. Relevant only if periodType = .Month 78 | self.calendarView.configuration.otherMonthTextColor = UIColor(hexString: "6f787c") 79 | 80 | // Set other month background color. Relevant only if periodType = .Month 81 | self.calendarView.configuration.otherMonthBackgroundColor = UIColor(hexString: "E7E7E7") 82 | 83 | // Set week text color 84 | self.calendarView.configuration.weekLabelTextColor = UIColor(hexString: "6f787c") 85 | 86 | // Set start day. Available type: .Monday, Sunday 87 | self.calendarView.configuration.startDayType = .monday 88 | 89 | // Set number of letters presented in the week days label 90 | self.calendarView.configuration.lettersInWeekDayLabel = .one 91 | 92 | // Set day text font 93 | self.calendarView.configuration.dayTextFont = UIFont.systemFont(ofSize: 12) 94 | 95 | //Set week's day name font 96 | self.calendarView.configuration.weekLabelFont = UIFont.systemFont(ofSize: 12) 97 | 98 | //Set day view size. It includes border width if selectedDayType = .Border 99 | self.calendarView.configuration.dayViewSize = CGSize(width: 24, height: 24) 100 | 101 | //Set height of row with week's days 102 | self.calendarView.configuration.rowHeight = 30 103 | 104 | // Set height of week's days names view 105 | self.calendarView.configuration.weekLabelHeight = 25 106 | 107 | // To commit all configuration changes execute reloadView method 108 | self.calendarView.reloadView() 109 | } 110 | 111 | func setTitleWithDate(_ date: Date) { 112 | self.dateFormatter.dateFormat = "MMMM yy" 113 | self.navigationItem.title = self.dateFormatter.string(from: date) 114 | } 115 | 116 | func setUpDays() { 117 | for i in 0...self.daysRange { 118 | let day = self.dateByIndex(i) 119 | if let randColor = self.randColor() { 120 | let dayColors = DayColors(backgroundColor: randColor, textColor: UIColor.white) 121 | self.dayColors[day] = dayColors 122 | } else { 123 | self.dayColors[day] = nil 124 | } 125 | } 126 | } 127 | 128 | func randColor() -> UIColor? { 129 | if arc4random() % 2 == 0 { 130 | let colorIndex = Int(arc4random()) % self.colors.count 131 | let color = self.colors[colorIndex] 132 | return color 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func numberOfSections(in tableView: UITableView) -> Int { 139 | return 1 140 | } 141 | 142 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 143 | return self.daysRange 144 | } 145 | 146 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 147 | let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell") as! TableViewCell 148 | 149 | let date = self.dateByIndex(indexPath.row) 150 | self.dateFormatter.dateStyle = DateFormatter.Style.long 151 | let dateString = self.dateFormatter.string(from: date) 152 | cell.dateLabel.text = dateString 153 | cell.colorView.backgroundColor = self.dayColors[date]?.backgroundColor 154 | cell.colorView.clipsToBounds = true 155 | cell.colorView.layer.cornerRadius = cell.colorView.width() / 2 156 | 157 | return cell 158 | } 159 | 160 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 161 | // Prevent changing selected day when non user scroll is triggered. 162 | if !self.isScrollingAnimation { 163 | // Get all visible cells from tableview 164 | if let visibleCells = self.tableView.indexPathsForVisibleRows { 165 | if let cellIndexPath = visibleCells.first { 166 | // Get day by indexPath 167 | let day = self.dateByIndex(cellIndexPath.row) 168 | 169 | //Select day according to first visible cell in tableview 170 | self.calendarView.selectDate(day) 171 | } 172 | } 173 | } 174 | } 175 | 176 | func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { 177 | self.isScrollingAnimation = false 178 | } 179 | 180 | func dateByIndex(_ index: Int) -> Date { 181 | let startDay = ((Date() as NSDate).atStartOfDay() as NSDate).subtractingDays(self.daysRange / 2) 182 | let day = (startDay as NSDate).addingDays(index) 183 | return day 184 | } 185 | 186 | func scrollTableViewToDate(_ date: Date) { 187 | if let row = self.indexByDate(date) { 188 | let indexPath = IndexPath(row: row, section: 0) 189 | self.tableView.setContentOffset(self.tableView.contentOffset, animated: false) 190 | self.isScrollingAnimation = true 191 | self.tableView.scrollToRow(at: indexPath, at: .top, animated: true) 192 | } 193 | } 194 | 195 | func indexByDate(_ date: Date) -> Int? { 196 | let startDay = ((Date() as NSDate).atStartOfDay() as NSDate).subtractingDays(self.daysRange / 2) 197 | let index = (date as NSDate).days(after: startDay) 198 | if index >= 0 && index <= self.daysRange { 199 | return index 200 | } else { 201 | return nil 202 | } 203 | } 204 | 205 | //MARK: MJCalendarViewDelegate 206 | func calendar(_ calendarView: MJCalendarView, didChangePeriod periodDate: Date, bySwipe: Bool) { 207 | // Sets month name according to presented dates 208 | self.setTitleWithDate(periodDate) 209 | 210 | // bySwipe diffrentiate changes made from swipes or select date method 211 | if bySwipe { 212 | // Scroll to relevant date in tableview 213 | self.scrollTableViewToDate(periodDate) 214 | } 215 | } 216 | 217 | func calendar(_ calendarView: MJCalendarView, backgroundForDate date: Date) -> UIColor? { 218 | return self.dayColors[date]?.backgroundColor 219 | } 220 | 221 | func calendar(_ calendarView: MJCalendarView, textColorForDate date: Date) -> UIColor? { 222 | return self.dayColors[date]?.textColor 223 | } 224 | 225 | func calendar(_ calendarView: MJCalendarView, didSelectDate date: Date) { 226 | self.scrollTableViewToDate(date) 227 | } 228 | 229 | //MARK: Toolbar actions 230 | @IBAction func didTapMonth(_ sender: AnyObject) { 231 | self.animateToPeriod(.month) 232 | } 233 | 234 | @IBAction func didTapThreeWeeks(_ sender: AnyObject) { 235 | self.animateToPeriod(.threeWeeks) 236 | } 237 | 238 | @IBAction func didTapTwoWeeks(_ sender: AnyObject) { 239 | self.animateToPeriod(.twoWeeks) 240 | } 241 | 242 | @IBAction func didTapOneWeek(_ sender: AnyObject) { 243 | self.animateToPeriod(.oneWeek) 244 | } 245 | 246 | func animateToPeriod(_ period: MJConfiguration.PeriodType) { 247 | self.tableView.setContentOffset(self.tableView.contentOffset, animated: false) 248 | 249 | self.calendarView.animateToPeriodType(period, duration: 0.2, animations: { (calendarHeight) -> Void in 250 | // In animation block you can add your own animation. To adapat UI to new calendar height you can use calendarHeight param 251 | self.calendarViewHeight.constant = calendarHeight 252 | self.view.layoutIfNeeded() 253 | }, completion: nil) 254 | } 255 | 256 | } 257 | 258 | -------------------------------------------------------------------------------- /Pod/Classes/MJCalendarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MJCalendarView.swift 3 | // Pods 4 | // 5 | // Created by Michał Jackowski on 18.09.2015. 6 | // 7 | // 8 | 9 | import UIKit 10 | import NSDate_Escort 11 | import UIView_JMFrame 12 | 13 | public protocol MJCalendarViewDelegate: NSObjectProtocol { 14 | func calendar(_ calendarView: MJCalendarView, didChangePeriod periodDate: Date, bySwipe: Bool) 15 | func calendar(_ calendarView: MJCalendarView, didSelectDate date: Date) 16 | func calendar(_ calendarView: MJCalendarView, backgroundForDate date: Date) -> UIColor? 17 | func calendar(_ calendarView: MJCalendarView, textColorForDate date: Date) -> UIColor? 18 | } 19 | 20 | open class MJCalendarView: UIView, UIScrollViewDelegate, MJComponentDelegate { 21 | open var configuration: MJConfiguration 22 | var periods: [MJPeriodView]? 23 | var weekLabelsView: MJWeekLabelsView? 24 | var periodsContainerView: UIScrollView? 25 | 26 | var date: Date 27 | var visiblePeriodDate: Date! 28 | var currentFrame = CGRect.zero 29 | weak open var calendarDelegate: MJCalendarViewDelegate? 30 | var isAnimating = false 31 | 32 | var currentPage: Int! 33 | 34 | required public init?(coder aDecoder: NSCoder) { 35 | self.configuration = MJConfiguration.getDefault() 36 | self.date = NSDate().atStartOfDay() as Date 37 | super.init(coder: aDecoder) 38 | self.visiblePeriodDate = self.startDate(self.date, withOtherMonth: false) 39 | self.configureViews() 40 | } 41 | 42 | override init(frame: CGRect) { 43 | self.configuration = MJConfiguration.getDefault() 44 | self.date = NSDate().atStartOfDay() 45 | super.init(frame: frame) 46 | self.visiblePeriodDate = self.startDate(self.date, withOtherMonth: false) 47 | self.configureViews() 48 | } 49 | 50 | func configureViews() { 51 | self.weekLabelsView = MJWeekLabelsView(delegate: self) 52 | self.addSubview(self.weekLabelsView!) 53 | 54 | self.periodsContainerView = UIScrollView(frame: CGRect.zero) 55 | self.periodsContainerView!.isPagingEnabled = true 56 | self.periodsContainerView!.delegate = self 57 | self.periodsContainerView?.showsHorizontalScrollIndicator = false 58 | self.addSubview(self.periodsContainerView!) 59 | 60 | self.setPeriodViews() 61 | } 62 | 63 | func setPeriodViews() { 64 | let visibleDate = self.visiblePeriodDate 65 | let previousDate = self.previousPeriodDate(visibleDate!, withOtherMonth: true) 66 | let currentDate = self.startDate(visibleDate!, withOtherMonth: true) 67 | let nextDate = self.nextPeriodDate(visibleDate!, withOtherMonth: true) 68 | 69 | if var periodViews = self.periods { 70 | if self.shouldChangePeriodsRange() { 71 | if self.periods?.count == 3 { 72 | periodViews[0].date = previousDate 73 | periodViews[1].date = currentDate 74 | periodViews[2].date = nextDate 75 | 76 | self.currentPage = 1 77 | self.setPeriodFrames() 78 | } else { 79 | self.createPeriodsViews(previousDate, currentDate: currentDate, nextDate: nextDate) 80 | self.setPeriodFrames() 81 | } 82 | } else { 83 | for periodView in periodViews { 84 | periodView.configureViews() 85 | } 86 | } 87 | } else { 88 | self.createPeriodsViews(previousDate, currentDate: currentDate, nextDate: nextDate) 89 | } 90 | } 91 | 92 | func createPeriodsViews(_ previousDate: Date, currentDate: Date, nextDate: Date) { 93 | self.clearView() 94 | self.periods = [] 95 | 96 | let previosPeriodView = MJPeriodView(date: previousDate, delegate: self) 97 | if !isDateEarlierThanMin(previosPeriodView.endingPeriodDate()) { 98 | self.periodsContainerView!.addSubview(previosPeriodView) 99 | self.periods!.append(previosPeriodView) 100 | } 101 | 102 | let currentPeriodView = MJPeriodView(date: currentDate, delegate: self) 103 | self.periodsContainerView!.addSubview(currentPeriodView) 104 | self.periods!.append(currentPeriodView) 105 | 106 | let nextPeriodView = MJPeriodView(date: nextDate, delegate: self) 107 | if !isDateLaterThanMax(nextPeriodView.startingPeriodDate()) { 108 | self.periodsContainerView!.addSubview(nextPeriodView) 109 | self.periods!.append(nextPeriodView) 110 | } 111 | 112 | self.currentPage = self.periods!.index(of: currentPeriodView) 113 | } 114 | 115 | func shouldChangePeriodsRange() -> Bool { 116 | let startDateOfPeriod = self.visiblePeriodDate 117 | let endDateOfPeriod = nextPeriodDate(self.visiblePeriodDate, withOtherMonth: false) 118 | return !(self.isDateEarlierThanMin(startDateOfPeriod!) || self.isDateLaterThanMax(endDateOfPeriod)) 119 | } 120 | 121 | func isDateEarlierThanMin(_ date: Date) -> Bool { 122 | if let minDate = (configuration.minDate as? NSDate)?.atStartOfDay() { 123 | if (date as NSDate).isEarlierThanDate(minDate) { 124 | return true 125 | } 126 | } 127 | 128 | return false 129 | } 130 | 131 | func isDateLaterThanMax(_ date: Date) -> Bool { 132 | if let maxDate = (configuration.maxDate as? NSDate)?.atEndOfDay() { 133 | if (date as NSDate).isLaterThanDate(maxDate) { 134 | return true 135 | } 136 | } 137 | 138 | return false 139 | } 140 | 141 | override open func layoutSubviews() { 142 | if !self.currentFrame.equalTo(self.frame) && !isAnimating { 143 | self.currentFrame = self.frame 144 | 145 | let weekLabelsViewHeight = self.configuration.weekLabelHeight 146 | self.periodsContainerView!.frame = CGRect(x: 0, y: weekLabelsViewHeight, width: self.width(), height: self.height() - weekLabelsViewHeight) 147 | 148 | self.setPeriodFrames() 149 | } 150 | } 151 | 152 | func setPeriodFrames() { 153 | let mod7 = self.width().truncatingRemainder(dividingBy: 7) 154 | let width = self.width() - mod7 155 | let x = ceil(mod7 / 2) 156 | 157 | self.weekLabelsView?.frame = CGRect(x: x, y: 0, width: width, height: self.configuration.weekLabelHeight) 158 | 159 | for (index, period) in (self.periods!).enumerated() { 160 | 161 | period.frame = CGRect(x: CGFloat(index) * self.width() + x,y: 0,width: width, height: self.periodHeight(self.configuration.periodType)) 162 | } 163 | 164 | self.periodsContainerView!.contentSize = CGSize(width: self.width() * CGFloat(self.periods!.count), height: self.height() - self.configuration.weekLabelHeight) 165 | self.periodsContainerView!.contentOffset.x = self.frame.width * CGFloat(self.currentPage) 166 | } 167 | 168 | func periodHeight(_ periodType: MJConfiguration.PeriodType) -> CGFloat { 169 | return CGFloat(periodType.weeksCount()) * self.configuration.rowHeight 170 | } 171 | 172 | func startDate(_ date: Date, withOtherMonth: Bool) -> Date { 173 | if self.configuration.periodType == .month { 174 | let beginningOfMonth = (date as NSDate).atStartOfMonth() 175 | if withOtherMonth { 176 | return self.startWeekDay(beginningOfMonth) 177 | } else { 178 | return beginningOfMonth 179 | } 180 | 181 | } else { 182 | return self.startWeekDay(date) 183 | } 184 | } 185 | 186 | func startWeekDay(_ date: Date) -> Date { 187 | let delta = self.configuration.startDayType == .monday ? 2 : 1 188 | var daysToSubstract = (date as NSDate).weekday - delta 189 | if daysToSubstract < 0 { 190 | daysToSubstract += 7 191 | } 192 | return (date as NSDate).subtractingDays(daysToSubstract) 193 | } 194 | 195 | func nextPeriodDate(_ date: Date, withOtherMonth: Bool) -> Date { 196 | return self.periodDate(date, isNext: true, withOtherMonth: withOtherMonth) 197 | } 198 | 199 | func previousPeriodDate(_ date: Date, withOtherMonth: Bool) -> Date { 200 | return self.periodDate(date, isNext: false, withOtherMonth: withOtherMonth) 201 | } 202 | 203 | func periodDate(_ date: Date, isNext: Bool, withOtherMonth: Bool) -> Date { 204 | let isNextFactor = isNext ? 1 : -1 205 | switch self.configuration.periodType { 206 | case .month: 207 | let otherMonthDate = (date as NSDate).addingMonths(1 * isNextFactor) 208 | return self.startDate(otherMonthDate, withOtherMonth: withOtherMonth) 209 | case .threeWeeks: return (date as NSDate).addingDays((3 * isNextFactor) * 7) 210 | case .twoWeeks: return (date as NSDate).addingDays((2 * isNextFactor) * 7) 211 | case .oneWeek: return (date as NSDate).addingDays((1 * isNextFactor) * 7) 212 | } 213 | } 214 | 215 | open func selectDate(_ date: Date) { 216 | let validatedDate = dateInRange(date) 217 | if !self.isDateAlreadyShown(validatedDate) { 218 | let periodDate = self.startDate(validatedDate, withOtherMonth: false) 219 | self.visiblePeriodDate = validatedDate.timeIntervalSince1970 < self.date.timeIntervalSince1970 220 | ? self.retroPeriodDate(periodDate) : periodDate 221 | self.calendarDelegate?.calendar(self, didChangePeriod: periodDate, bySwipe: false) 222 | } 223 | self.date = validatedDate 224 | self.setPeriodViews() 225 | } 226 | 227 | func selectNewPeriod(_ date: Date) { 228 | let validatedDate = dateInRange(date) 229 | if !self.isDateAlreadyShown(validatedDate) { 230 | let periodDate = self.startDate(validatedDate, withOtherMonth: false) 231 | self.visiblePeriodDate = periodDate 232 | self.calendarDelegate?.calendar(self, didChangePeriod: periodDate, bySwipe: false) 233 | } 234 | self.date = validatedDate 235 | self.setPeriodViews() 236 | } 237 | 238 | open func moveToNextPeriod() { 239 | if let nextPeriodDate = self.periods?.last?.startingPeriodDate() { 240 | selectNewPeriod(nextPeriodDate as Date) 241 | calendarDelegate?.calendar(self, didChangePeriod: nextPeriodDate as Date, bySwipe: true) 242 | } 243 | } 244 | 245 | open func moveToPreviousPeriod() { 246 | if let previousPeriodDate = self.periods?.first?.startingPeriodDate() { 247 | selectNewPeriod(previousPeriodDate as Date) 248 | calendarDelegate?.calendar(self, didChangePeriod: previousPeriodDate as Date, bySwipe: true) 249 | } 250 | } 251 | 252 | func dateInRange(_ date: Date) -> Date { 253 | if isDateEarlierThanMin(date) { 254 | return (configuration.minDate! as NSDate).atStartOfDay() 255 | } else { 256 | return date 257 | } 258 | } 259 | 260 | func retroPeriodDate(_ periodDate: Date) -> Date { 261 | switch self.configuration.periodType { 262 | case .month: return periodDate 263 | case .threeWeeks: return (periodDate as NSDate).addingDays(-14) 264 | case .twoWeeks: return (periodDate as NSDate).addingDays(-7) 265 | case .oneWeek: return periodDate 266 | } 267 | } 268 | 269 | func currentPeriod() -> MJPeriodView { 270 | return self.periods![self.currentPage] 271 | } 272 | 273 | func isDateAlreadyShown(_ date: Date) -> Bool { 274 | if self.configuration.periodType == .month { 275 | return (date as NSDate).atStartOfMonth() == (self.visiblePeriodDate as NSDate).atStartOfMonth() 276 | } else { 277 | return date.timeIntervalSince1970 >= self.currentPeriod().startingDate().timeIntervalSince1970 278 | && date.timeIntervalSince1970 <= self.currentPeriod().endingDate().timeIntervalSince1970 279 | } 280 | } 281 | 282 | // MARK: Calendar delegate 283 | 284 | func componentView(_ componentView: MJComponentView, isDateSelected date: Date) -> Bool { 285 | return self.date == date 286 | } 287 | 288 | func configurationWithComponent(_ componentView: MJComponentView) -> MJConfiguration { 289 | return self.configuration 290 | } 291 | 292 | func componentView(_ componentView: MJComponentView, didSelectDate date: Date) { 293 | self.selectDate(date) 294 | self.calendarDelegate?.calendar(self, didSelectDate: date) 295 | } 296 | 297 | func isBeingAnimatedWithComponentView(_ componentView: MJComponentView) -> Bool { 298 | return self.isAnimating 299 | } 300 | 301 | func componentView(_ componentView: MJComponentView, backgroundColorForDate date: Date) -> UIColor? { 302 | return self.calendarDelegate?.calendar(self, backgroundForDate: date) 303 | } 304 | 305 | func componentView(_ componentView: MJComponentView, textColorForDate date: Date) -> UIColor? { 306 | return self.calendarDelegate?.calendar(self, textColorForDate: date) 307 | } 308 | 309 | func isDateOutOfRange(_ componentView: MJComponentView, date: Date) -> Bool { 310 | return isDateLaterThanMax(date) || isDateEarlierThanMin(date) 311 | } 312 | 313 | // MARK: UIScrollViewDelegate 314 | 315 | open func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { 316 | let pageWidth = scrollView.frame.width 317 | let ratio = scrollView.contentOffset.x / pageWidth 318 | let page = Int(ratio) 319 | 320 | let periodDate = self.periodDateFromPage(page) 321 | if self.visiblePeriodDate != periodDate { 322 | self.currentPage = page 323 | self.visiblePeriodDate = periodDate 324 | self.calendarDelegate?.calendar(self, didChangePeriod: periodDate, bySwipe: true) 325 | if self.configuration.selectDayOnPeriodChange { 326 | self.selectDate(periodDate) 327 | } else { 328 | self.setPeriodViews() 329 | } 330 | } 331 | } 332 | 333 | func periodDateFromPage(_ page: Int) -> Date { 334 | return periods![page].startingPeriodDate() as Date 335 | } 336 | 337 | open func reloadView() { 338 | self.visiblePeriodDate = self.recalculatedVisibleDate(false) 339 | self.clearView() 340 | self.setPeriodViews() 341 | self.setPeriodFrames() 342 | self.weekLabelsView?.updateView() 343 | } 344 | 345 | func clearView() { 346 | if let periodViews = self.periods { 347 | for period in periodViews { 348 | period.removeFromSuperview() 349 | } 350 | } 351 | 352 | self.periods = nil 353 | self.currentFrame = CGRect.zero 354 | } 355 | 356 | func recalculatedVisibleDate(_ withOtherMonth: Bool) -> Date { 357 | let visibleDate = self.currentPeriod().isDateInPeriod(self.date) ? self.date : self.visiblePeriodDate 358 | let startDate = self.startDate(visibleDate!, withOtherMonth: withOtherMonth) 359 | if self.configuration.periodType == .month || self.configuration.periodType == .oneWeek { 360 | return startDate 361 | } else { 362 | let weekIndex = self.weekIndexByStartDate(startDate) + 1 363 | let weekCount = self.configuration.periodType.weeksCount() 364 | let visibleIndex = weekIndex - weekCount > 0 ? weekIndex - weekCount : 0 365 | return self.currentPeriod().weeks![visibleIndex].date 366 | } 367 | } 368 | 369 | func weekIndexByStartDate(_ startDate: Date) -> Int { 370 | for (index, week) in (self.currentPeriod().weeks!).enumerated() { 371 | if week.date == startDate { 372 | return index 373 | } 374 | } 375 | 376 | return 0 377 | } 378 | 379 | open func reloadDayViews() { 380 | for periodView in self.periods! { 381 | for weekView in periodView.weeks! { 382 | for dayView in weekView.days! { 383 | dayView.updateView() 384 | } 385 | } 386 | } 387 | } 388 | 389 | open func animateToPeriodType(_ periodType: MJConfiguration.PeriodType, duration: TimeInterval, animations: @escaping (_ calendarHeight: CGFloat) -> Void, completion: ((Bool) -> Void)?) { 390 | let previousVisibleDate = self.visiblePeriodDate 391 | let previousPeriodType = self.configuration.periodType 392 | 393 | self.configuration.periodType = periodType 394 | let yDelta = self.periodYDelta(periodType, previousVisibleDate: previousVisibleDate!) 395 | 396 | if periodType.weeksCount() > previousPeriodType.weeksCount() { 397 | self.reloadView() 398 | self.layoutIfNeeded() 399 | 400 | self.currentPeriod().setY(self.currentPeriod().y() + yDelta) 401 | self.currentPeriod().setHeight(self.periodHeight(previousPeriodType) - yDelta) 402 | 403 | self.performAnimation(true, periodType: periodType, yDelta: yDelta, duration: duration, animations: animations, completion: completion) 404 | } else { 405 | self.performAnimation(false, periodType: periodType, yDelta: yDelta, duration: duration, animations: animations, completion: completion) 406 | } 407 | } 408 | 409 | func periodYDelta(_ periodType: MJConfiguration.PeriodType, previousVisibleDate: Date) -> CGFloat { 410 | let visiblePeriodDatePreview = self.recalculatedVisibleDate(true) 411 | let deltaVisiblePeriod = visiblePeriodDatePreview.timeIntervalSince1970 - previousVisibleDate.timeIntervalSince1970 412 | let weekIndexDelta = ceil(deltaVisiblePeriod / (3600 * 24 * 7)) 413 | return CGFloat(weekIndexDelta) * self.configuration.rowHeight 414 | } 415 | 416 | func performAnimation(_ animateToBiggerSize: Bool, periodType: MJConfiguration.PeriodType, yDelta: CGFloat, duration: TimeInterval, animations: @escaping (_ calendarHeight: CGFloat) -> Void, completion: ((Bool) -> Void)?) { 417 | self.isAnimating = true 418 | UIView.animate(withDuration: duration, animations: { () -> Void in 419 | animations(self.periodHeight(periodType) + self.configuration.weekLabelHeight) 420 | if animateToBiggerSize { 421 | self.currentPeriod().setY(0) 422 | self.currentPeriod().setHeight(self.periodHeight(periodType)) 423 | } else { 424 | self.currentPeriod().setY(self.currentPeriod().y() - yDelta) 425 | self.currentPeriod().setHeight(self.periodHeight(periodType) + yDelta) 426 | } 427 | 428 | }) { (completed) -> Void in 429 | self.isAnimating = false 430 | if !animateToBiggerSize { 431 | self.reloadView() 432 | self.setPeriodFrames() 433 | } 434 | self.calendarDelegate?.calendar(self, didChangePeriod: self.visiblePeriodDate, bySwipe: false) 435 | 436 | if let completionBlock = completion { 437 | completionBlock(completed) 438 | } 439 | } 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /Example/MJCalendar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 11 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 12 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; }; 13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 14 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 15 | 951B92E51CA5E55D0053F6FF /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951B92E41CA5E55D0053F6FF /* DatePickerViewController.swift */; }; 16 | 959265961BE97699008909B1 /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 959265951BE97699008909B1 /* TableViewCell.swift */; }; 17 | 98A40221F33E4459EE2FF461 /* Pods_MJCalendar_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84AFB1BE41058B199AC3BC0F /* Pods_MJCalendar_Tests.framework */; }; 18 | D26D3938E1DFFB1FEBA96222 /* Pods_MJCalendar_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5F521FD89CFED3DE5943CFFF /* Pods_MJCalendar_Example.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 27 | remoteInfo = MJCalendar; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 3170D813477513126155BA09 /* Pods-MJCalendar_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MJCalendar_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MJCalendar_Tests/Pods-MJCalendar_Tests.release.xcconfig"; sourceTree = ""; }; 33 | 31C1E9C6B7E39D25A9B8F58B /* MJCalendar.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = MJCalendar.podspec; path = ../MJCalendar.podspec; sourceTree = ""; }; 34 | 34DBACC5AA4CC356BE052FE1 /* Pods-MJCalendar_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MJCalendar_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-MJCalendar_Example/Pods-MJCalendar_Example.release.xcconfig"; sourceTree = ""; }; 35 | 371FC63E9CFC678EAB6598EB /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 36 | 447EF6FFC4874DB091928106 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 37 | 5F521FD89CFED3DE5943CFFF /* Pods_MJCalendar_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MJCalendar_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 607FACD01AFB9204008FA782 /* MJCalendar_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MJCalendar_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 42 | 607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 44 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 45 | 607FACE51AFB9204008FA782 /* MJCalendar_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MJCalendar_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 6F33228D779500E5E1D193CA /* Pods-MJCalendar_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MJCalendar_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MJCalendar_Example/Pods-MJCalendar_Example.debug.xcconfig"; sourceTree = ""; }; 48 | 84AFB1BE41058B199AC3BC0F /* Pods_MJCalendar_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MJCalendar_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 951B92E41CA5E55D0053F6FF /* DatePickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = ""; }; 50 | 959265951BE97699008909B1 /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; 51 | F554992B36CD359440A3814C /* Pods-MJCalendar_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MJCalendar_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MJCalendar_Tests/Pods-MJCalendar_Tests.debug.xcconfig"; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | D26D3938E1DFFB1FEBA96222 /* Pods_MJCalendar_Example.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 98A40221F33E4459EE2FF461 /* Pods_MJCalendar_Tests.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 607FACC71AFB9204008FA782 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 78 | 607FACD21AFB9204008FA782 /* Example for MJCalendar */, 79 | 607FACE81AFB9204008FA782 /* Tests */, 80 | 607FACD11AFB9204008FA782 /* Products */, 81 | 806EA84B95F4C047E427E4B9 /* Pods */, 82 | 993FFF9F509DA8127B51AC93 /* Frameworks */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 607FACD11AFB9204008FA782 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 607FACD01AFB9204008FA782 /* MJCalendar_Example.app */, 90 | 607FACE51AFB9204008FA782 /* MJCalendar_Tests.xctest */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | 607FACD21AFB9204008FA782 /* Example for MJCalendar */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 99 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 100 | 951B92E41CA5E55D0053F6FF /* DatePickerViewController.swift */, 101 | 959265951BE97699008909B1 /* TableViewCell.swift */, 102 | 607FACD91AFB9204008FA782 /* Main.storyboard */, 103 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 104 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 105 | 607FACD31AFB9204008FA782 /* Supporting Files */, 106 | ); 107 | name = "Example for MJCalendar"; 108 | path = MJCalendar; 109 | sourceTree = ""; 110 | }; 111 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 607FACD41AFB9204008FA782 /* Info.plist */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | 607FACE81AFB9204008FA782 /* Tests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 607FACE91AFB9204008FA782 /* Supporting Files */, 123 | ); 124 | path = Tests; 125 | sourceTree = ""; 126 | }; 127 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 607FACEA1AFB9204008FA782 /* Info.plist */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 31C1E9C6B7E39D25A9B8F58B /* MJCalendar.podspec */, 139 | 371FC63E9CFC678EAB6598EB /* README.md */, 140 | 447EF6FFC4874DB091928106 /* LICENSE */, 141 | ); 142 | name = "Podspec Metadata"; 143 | sourceTree = ""; 144 | }; 145 | 806EA84B95F4C047E427E4B9 /* Pods */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 6F33228D779500E5E1D193CA /* Pods-MJCalendar_Example.debug.xcconfig */, 149 | 34DBACC5AA4CC356BE052FE1 /* Pods-MJCalendar_Example.release.xcconfig */, 150 | F554992B36CD359440A3814C /* Pods-MJCalendar_Tests.debug.xcconfig */, 151 | 3170D813477513126155BA09 /* Pods-MJCalendar_Tests.release.xcconfig */, 152 | ); 153 | name = Pods; 154 | sourceTree = ""; 155 | }; 156 | 993FFF9F509DA8127B51AC93 /* Frameworks */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 5F521FD89CFED3DE5943CFFF /* Pods_MJCalendar_Example.framework */, 160 | 84AFB1BE41058B199AC3BC0F /* Pods_MJCalendar_Tests.framework */, 161 | ); 162 | name = Frameworks; 163 | sourceTree = ""; 164 | }; 165 | /* End PBXGroup section */ 166 | 167 | /* Begin PBXNativeTarget section */ 168 | 607FACCF1AFB9204008FA782 /* MJCalendar_Example */ = { 169 | isa = PBXNativeTarget; 170 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MJCalendar_Example" */; 171 | buildPhases = ( 172 | E456B4AB0773D606173B7E03 /* [CP] Check Pods Manifest.lock */, 173 | 607FACCC1AFB9204008FA782 /* Sources */, 174 | 607FACCD1AFB9204008FA782 /* Frameworks */, 175 | 607FACCE1AFB9204008FA782 /* Resources */, 176 | 876AEAC0CD74C737A111D59E /* [CP] Embed Pods Frameworks */, 177 | 0B7D175DD701C5CB42E5A887 /* [CP] Copy Pods Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = MJCalendar_Example; 184 | productName = MJCalendar; 185 | productReference = 607FACD01AFB9204008FA782 /* MJCalendar_Example.app */; 186 | productType = "com.apple.product-type.application"; 187 | }; 188 | 607FACE41AFB9204008FA782 /* MJCalendar_Tests */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MJCalendar_Tests" */; 191 | buildPhases = ( 192 | D9EA78D936F1DB786B2703A2 /* [CP] Check Pods Manifest.lock */, 193 | 607FACE11AFB9204008FA782 /* Sources */, 194 | 607FACE21AFB9204008FA782 /* Frameworks */, 195 | 607FACE31AFB9204008FA782 /* Resources */, 196 | D52780D969A38EB46CCC2886 /* [CP] Embed Pods Frameworks */, 197 | 8605E371BD0B2E68923FBFEA /* [CP] Copy Pods Resources */, 198 | ); 199 | buildRules = ( 200 | ); 201 | dependencies = ( 202 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */, 203 | ); 204 | name = MJCalendar_Tests; 205 | productName = Tests; 206 | productReference = 607FACE51AFB9204008FA782 /* MJCalendar_Tests.xctest */; 207 | productType = "com.apple.product-type.bundle.unit-test"; 208 | }; 209 | /* End PBXNativeTarget section */ 210 | 211 | /* Begin PBXProject section */ 212 | 607FACC81AFB9204008FA782 /* Project object */ = { 213 | isa = PBXProject; 214 | attributes = { 215 | LastSwiftMigration = 0720; 216 | LastSwiftUpdateCheck = 0720; 217 | LastUpgradeCheck = 0820; 218 | ORGANIZATIONNAME = CocoaPods; 219 | TargetAttributes = { 220 | 607FACCF1AFB9204008FA782 = { 221 | CreatedOnToolsVersion = 6.3.1; 222 | LastSwiftMigration = 0820; 223 | }; 224 | 607FACE41AFB9204008FA782 = { 225 | CreatedOnToolsVersion = 6.3.1; 226 | LastSwiftMigration = 0820; 227 | TestTargetID = 607FACCF1AFB9204008FA782; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "MJCalendar" */; 232 | compatibilityVersion = "Xcode 3.2"; 233 | developmentRegion = English; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 607FACC71AFB9204008FA782; 240 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 607FACCF1AFB9204008FA782 /* MJCalendar_Example */, 245 | 607FACE41AFB9204008FA782 /* MJCalendar_Tests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 607FACCE1AFB9204008FA782 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | 607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */, 256 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 257 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 607FACE31AFB9204008FA782 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXResourcesBuildPhase section */ 269 | 270 | /* Begin PBXShellScriptBuildPhase section */ 271 | 0B7D175DD701C5CB42E5A887 /* [CP] Copy Pods Resources */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | inputPaths = ( 277 | ); 278 | name = "[CP] Copy Pods Resources"; 279 | outputPaths = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MJCalendar_Example/Pods-MJCalendar_Example-resources.sh\"\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | 8605E371BD0B2E68923FBFEA /* [CP] Copy Pods Resources */ = { 287 | isa = PBXShellScriptBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | ); 291 | inputPaths = ( 292 | ); 293 | name = "[CP] Copy Pods Resources"; 294 | outputPaths = ( 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | shellPath = /bin/sh; 298 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MJCalendar_Tests/Pods-MJCalendar_Tests-resources.sh\"\n"; 299 | showEnvVarsInLog = 0; 300 | }; 301 | 876AEAC0CD74C737A111D59E /* [CP] Embed Pods Frameworks */ = { 302 | isa = PBXShellScriptBuildPhase; 303 | buildActionMask = 2147483647; 304 | files = ( 305 | ); 306 | inputPaths = ( 307 | ); 308 | name = "[CP] Embed Pods Frameworks"; 309 | outputPaths = ( 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | shellPath = /bin/sh; 313 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MJCalendar_Example/Pods-MJCalendar_Example-frameworks.sh\"\n"; 314 | showEnvVarsInLog = 0; 315 | }; 316 | D52780D969A38EB46CCC2886 /* [CP] Embed Pods Frameworks */ = { 317 | isa = PBXShellScriptBuildPhase; 318 | buildActionMask = 2147483647; 319 | files = ( 320 | ); 321 | inputPaths = ( 322 | ); 323 | name = "[CP] Embed Pods Frameworks"; 324 | outputPaths = ( 325 | ); 326 | runOnlyForDeploymentPostprocessing = 0; 327 | shellPath = /bin/sh; 328 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MJCalendar_Tests/Pods-MJCalendar_Tests-frameworks.sh\"\n"; 329 | showEnvVarsInLog = 0; 330 | }; 331 | D9EA78D936F1DB786B2703A2 /* [CP] Check Pods Manifest.lock */ = { 332 | isa = PBXShellScriptBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | ); 336 | inputPaths = ( 337 | ); 338 | name = "[CP] Check Pods Manifest.lock"; 339 | outputPaths = ( 340 | ); 341 | runOnlyForDeploymentPostprocessing = 0; 342 | shellPath = /bin/sh; 343 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 344 | showEnvVarsInLog = 0; 345 | }; 346 | E456B4AB0773D606173B7E03 /* [CP] Check Pods Manifest.lock */ = { 347 | isa = PBXShellScriptBuildPhase; 348 | buildActionMask = 2147483647; 349 | files = ( 350 | ); 351 | inputPaths = ( 352 | ); 353 | name = "[CP] Check Pods Manifest.lock"; 354 | outputPaths = ( 355 | ); 356 | runOnlyForDeploymentPostprocessing = 0; 357 | shellPath = /bin/sh; 358 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; 359 | showEnvVarsInLog = 0; 360 | }; 361 | /* End PBXShellScriptBuildPhase section */ 362 | 363 | /* Begin PBXSourcesBuildPhase section */ 364 | 607FACCC1AFB9204008FA782 /* Sources */ = { 365 | isa = PBXSourcesBuildPhase; 366 | buildActionMask = 2147483647; 367 | files = ( 368 | 951B92E51CA5E55D0053F6FF /* DatePickerViewController.swift in Sources */, 369 | 959265961BE97699008909B1 /* TableViewCell.swift in Sources */, 370 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 371 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | 607FACE11AFB9204008FA782 /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | ); 380 | runOnlyForDeploymentPostprocessing = 0; 381 | }; 382 | /* End PBXSourcesBuildPhase section */ 383 | 384 | /* Begin PBXTargetDependency section */ 385 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = { 386 | isa = PBXTargetDependency; 387 | target = 607FACCF1AFB9204008FA782 /* MJCalendar_Example */; 388 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */; 389 | }; 390 | /* End PBXTargetDependency section */ 391 | 392 | /* Begin PBXVariantGroup section */ 393 | 607FACD91AFB9204008FA782 /* Main.storyboard */ = { 394 | isa = PBXVariantGroup; 395 | children = ( 396 | 607FACDA1AFB9204008FA782 /* Base */, 397 | ); 398 | name = Main.storyboard; 399 | sourceTree = ""; 400 | }; 401 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 402 | isa = PBXVariantGroup; 403 | children = ( 404 | 607FACDF1AFB9204008FA782 /* Base */, 405 | ); 406 | name = LaunchScreen.xib; 407 | sourceTree = ""; 408 | }; 409 | /* End PBXVariantGroup section */ 410 | 411 | /* Begin XCBuildConfiguration section */ 412 | 607FACED1AFB9204008FA782 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | ALWAYS_SEARCH_USER_PATHS = NO; 416 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 417 | CLANG_CXX_LIBRARY = "libc++"; 418 | CLANG_ENABLE_MODULES = YES; 419 | CLANG_ENABLE_OBJC_ARC = YES; 420 | CLANG_WARN_BOOL_CONVERSION = YES; 421 | CLANG_WARN_CONSTANT_CONVERSION = YES; 422 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 423 | CLANG_WARN_EMPTY_BODY = YES; 424 | CLANG_WARN_ENUM_CONVERSION = YES; 425 | CLANG_WARN_INFINITE_RECURSION = YES; 426 | CLANG_WARN_INT_CONVERSION = YES; 427 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 434 | ENABLE_STRICT_OBJC_MSGSEND = YES; 435 | ENABLE_TESTABILITY = YES; 436 | GCC_C_LANGUAGE_STANDARD = gnu99; 437 | GCC_DYNAMIC_NO_PIC = NO; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_OPTIMIZATION_LEVEL = 0; 440 | GCC_PREPROCESSOR_DEFINITIONS = ( 441 | "DEBUG=1", 442 | "$(inherited)", 443 | ); 444 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 447 | GCC_WARN_UNDECLARED_SELECTOR = YES; 448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 449 | GCC_WARN_UNUSED_FUNCTION = YES; 450 | GCC_WARN_UNUSED_VARIABLE = YES; 451 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 452 | MTL_ENABLE_DEBUG_INFO = YES; 453 | ONLY_ACTIVE_ARCH = YES; 454 | SDKROOT = iphoneos; 455 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 456 | }; 457 | name = Debug; 458 | }; 459 | 607FACEE1AFB9204008FA782 /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | ALWAYS_SEARCH_USER_PATHS = NO; 463 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 464 | CLANG_CXX_LIBRARY = "libc++"; 465 | CLANG_ENABLE_MODULES = YES; 466 | CLANG_ENABLE_OBJC_ARC = YES; 467 | CLANG_WARN_BOOL_CONVERSION = YES; 468 | CLANG_WARN_CONSTANT_CONVERSION = YES; 469 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 470 | CLANG_WARN_EMPTY_BODY = YES; 471 | CLANG_WARN_ENUM_CONVERSION = YES; 472 | CLANG_WARN_INFINITE_RECURSION = YES; 473 | CLANG_WARN_INT_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 476 | CLANG_WARN_UNREACHABLE_CODE = YES; 477 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | COPY_PHASE_STRIP = NO; 480 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 481 | ENABLE_NS_ASSERTIONS = NO; 482 | ENABLE_STRICT_OBJC_MSGSEND = YES; 483 | GCC_C_LANGUAGE_STANDARD = gnu99; 484 | GCC_NO_COMMON_BLOCKS = YES; 485 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 486 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 487 | GCC_WARN_UNDECLARED_SELECTOR = YES; 488 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 489 | GCC_WARN_UNUSED_FUNCTION = YES; 490 | GCC_WARN_UNUSED_VARIABLE = YES; 491 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 492 | MTL_ENABLE_DEBUG_INFO = NO; 493 | SDKROOT = iphoneos; 494 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 495 | VALIDATE_PRODUCT = YES; 496 | }; 497 | name = Release; 498 | }; 499 | 607FACF01AFB9204008FA782 /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | baseConfigurationReference = 6F33228D779500E5E1D193CA /* Pods-MJCalendar_Example.debug.xcconfig */; 502 | buildSettings = { 503 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 504 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 505 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 506 | CLANG_ENABLE_MODULES = YES; 507 | INFOPLIST_FILE = MJCalendar/Info.plist; 508 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 509 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 510 | MODULE_NAME = ExampleApp; 511 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 514 | SWIFT_VERSION = 3.0; 515 | TARGETED_DEVICE_FAMILY = "1,2"; 516 | }; 517 | name = Debug; 518 | }; 519 | 607FACF11AFB9204008FA782 /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | baseConfigurationReference = 34DBACC5AA4CC356BE052FE1 /* Pods-MJCalendar_Example.release.xcconfig */; 522 | buildSettings = { 523 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 524 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 525 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 526 | CLANG_ENABLE_MODULES = YES; 527 | INFOPLIST_FILE = MJCalendar/Info.plist; 528 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 529 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 530 | MODULE_NAME = ExampleApp; 531 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | SWIFT_VERSION = 3.0; 534 | TARGETED_DEVICE_FAMILY = "1,2"; 535 | }; 536 | name = Release; 537 | }; 538 | 607FACF31AFB9204008FA782 /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | baseConfigurationReference = F554992B36CD359440A3814C /* Pods-MJCalendar_Tests.debug.xcconfig */; 541 | buildSettings = { 542 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 543 | BUNDLE_LOADER = "$(TEST_HOST)"; 544 | FRAMEWORK_SEARCH_PATHS = ( 545 | "$(SDKROOT)/Developer/Library/Frameworks", 546 | "$(inherited)", 547 | ); 548 | GCC_PREPROCESSOR_DEFINITIONS = ( 549 | "DEBUG=1", 550 | "$(inherited)", 551 | ); 552 | INFOPLIST_FILE = Tests/Info.plist; 553 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 554 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 555 | PRODUCT_NAME = "$(TARGET_NAME)"; 556 | SWIFT_VERSION = 3.0; 557 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MJCalendar_Example.app/MJCalendar_Example"; 558 | }; 559 | name = Debug; 560 | }; 561 | 607FACF41AFB9204008FA782 /* Release */ = { 562 | isa = XCBuildConfiguration; 563 | baseConfigurationReference = 3170D813477513126155BA09 /* Pods-MJCalendar_Tests.release.xcconfig */; 564 | buildSettings = { 565 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 566 | BUNDLE_LOADER = "$(TEST_HOST)"; 567 | FRAMEWORK_SEARCH_PATHS = ( 568 | "$(SDKROOT)/Developer/Library/Frameworks", 569 | "$(inherited)", 570 | ); 571 | INFOPLIST_FILE = Tests/Info.plist; 572 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 573 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | SWIFT_VERSION = 3.0; 576 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MJCalendar_Example.app/MJCalendar_Example"; 577 | }; 578 | name = Release; 579 | }; 580 | /* End XCBuildConfiguration section */ 581 | 582 | /* Begin XCConfigurationList section */ 583 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "MJCalendar" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 607FACED1AFB9204008FA782 /* Debug */, 587 | 607FACEE1AFB9204008FA782 /* Release */, 588 | ); 589 | defaultConfigurationIsVisible = 0; 590 | defaultConfigurationName = Release; 591 | }; 592 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MJCalendar_Example" */ = { 593 | isa = XCConfigurationList; 594 | buildConfigurations = ( 595 | 607FACF01AFB9204008FA782 /* Debug */, 596 | 607FACF11AFB9204008FA782 /* Release */, 597 | ); 598 | defaultConfigurationIsVisible = 0; 599 | defaultConfigurationName = Release; 600 | }; 601 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "MJCalendar_Tests" */ = { 602 | isa = XCConfigurationList; 603 | buildConfigurations = ( 604 | 607FACF31AFB9204008FA782 /* Debug */, 605 | 607FACF41AFB9204008FA782 /* Release */, 606 | ); 607 | defaultConfigurationIsVisible = 0; 608 | defaultConfigurationName = Release; 609 | }; 610 | /* End XCConfigurationList section */ 611 | }; 612 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 613 | } 614 | -------------------------------------------------------------------------------- /Example/MJCalendar/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 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 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 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 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 | 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 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | --------------------------------------------------------------------------------