├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── LICENSE ├── Package.swift ├── README.md ├── Resources ├── MonthView.gif ├── ScrollView.gif └── WeekView.gif ├── Sources └── JHCalendar │ ├── Example │ └── ExampleView.swift │ ├── Model │ ├── DataModel.swift │ └── DateModel.swift │ ├── View │ ├── CalendarTitle.swift │ ├── DefaultCalendarCell.swift │ ├── JHCalendar.swift │ ├── Modifier │ │ └── JHCalendarModifier.swift │ ├── MonthView.swift │ ├── WeekBar.swift │ └── WeekView.swift │ └── ViewModel │ └── CalendarManager.swift └── Tests └── JHCalendarTests └── JHCalendarTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 LeeProgrammer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "JHCalendar", 8 | platforms: [ 9 | .iOS(.v14), 10 | .macOS(.v11) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "JHCalendar", 16 | targets: ["JHCalendar"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "JHCalendar", 27 | dependencies: []), 28 | .testTarget( 29 | name: "JHCalendarTests", 30 | dependencies: ["JHCalendar"]), 31 | ] 32 | ) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ``JHCalendar`` 2 | 3 | SwiftUI Customizable Calendar Library 4 | 5 | ![Version badge](https://img.shields.io/badge/version-2.0.0-green.svg) 6 | ![License](https://img.shields.io/github/license/jaeho0718/JHCalendar) 7 | 8 | ## Overview 9 | 10 | SwiftUI Customizable Calendar Library. 11 | 12 | ## Download 13 | 14 | Use swift package manager. 15 | 16 | https://github.com/jaeho0718/JHCalendar > Exact Version 2.0.0 17 | 18 | ## ScreenShot 19 | |WeekdayMode|MonthMode|ScrollDisplayMode| 20 | |-----------|---------|-----------------| 21 | |![WeekdayMode](./Resources/WeekView.gif)|![MonthMode](./Resources/MonthView.gif)|![ScrollDisplayMode](./Resources/ScrollView.gif)| 22 | 23 | ## Basic 24 | 25 | Check Sources/JHCalendar/ExampleView.swift 26 | 27 | ```swift 28 | import SwiftUI 29 | 30 | struct ContentView : View { 31 | 32 | @StateObject var calendarManager = CalendarManger(mode : .Month, 33 | startDate : .startDefault, 34 | endDate : .endDefault, 35 | startPoint : .current) 36 | 37 | var body : some View { 38 | JHCalendar(cellHeight : 50) { comp in 39 | DefaultCalendarDayView(component: component) 40 | } 41 | .environmentObject(calendarManager) 42 | } 43 | } 44 | ``` 45 | 46 | ## Usage 47 | 48 | - Declare CalendarManager 49 | 50 | - To use JHCalendar,CalendarManager sould be declared. (**Important**) 51 | 52 | ```swift 53 | CalendarManger(mode : .Month,startDate: .startDefault, endDate: .endDefault, startPoint: .currentDefault) 54 | ``` 55 | - mode : Calendar mode (Month/Week) (In macos,only support Month mode) 56 | - startDate : Calendar first date 57 | - endDate : Calendar last date 58 | - startPoint : Calendar start point when view appears. 59 | - you can get page info in CalendarManager.page.current 60 | 61 | - Declare JHCalendar 62 | 63 | ```swift 64 | JHCalendar(cellHeight : 50) { comp in 65 | DefaultCalendarDayView(component: component) 66 | } 67 | .environmentObject(calendarManager) 68 | ``` 69 | 70 | - content : The view that will be used to display the day content in the calendar. (you can use DefaultCalendarDayView) 71 | 72 | - component : A structure containing specific information about the day. 73 | 74 | ```swift 75 | struct CalendarComponent { 76 | var year : Int 77 | var month : Int 78 | var day : Int 79 | } 80 | ``` 81 | 82 | - Customize Calendar 83 | 84 | - You can customize all components of calendar by using Modifier. 85 | 86 | ```swift 87 | JHCalendar(content: { component in 88 | DefaultCalendarDayView(component: component) 89 | }) 90 | .customWeekdaySymbols(symbols : ["S","M,"T","Wed","T","F","S"]) 91 | .weekdaySymbolColor(color : .primary) 92 | .weekdayFont(font : .caption2) 93 | .calendarCellAccentColor(primary : .primary,secondary : .secondary) 94 | .showTitle(false) 95 | .showWeekBar(true) 96 | .calendarDisplayMode(.page) 97 | ``` 98 | 99 | |view modifier|content| 100 | |-------------|-------| 101 | |customWeekdaySymbols|set custom weekday symbol| 102 | |weekdaySymbolColor|set weekday color| 103 | |weekdayFont|set weekday font| 104 | |calendarCellAccentColor|set calendar accent Color.| 105 | |showTitle|set title visibility| 106 | |showWeekBar|set weekday visivility| 107 | |calendarDisplayMode|set calendar displaymode (page/scroll) (only support iOS)| 108 | -------------------------------------------------------------------------------- /Resources/MonthView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaeho0718/JHCalendar/e17452fb90d775aad9491475005d5a61d337127c/Resources/MonthView.gif -------------------------------------------------------------------------------- /Resources/ScrollView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaeho0718/JHCalendar/e17452fb90d775aad9491475005d5a61d337127c/Resources/ScrollView.gif -------------------------------------------------------------------------------- /Resources/WeekView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaeho0718/JHCalendar/e17452fb90d775aad9491475005d5a61d337127c/Resources/WeekView.gif -------------------------------------------------------------------------------- /Sources/JHCalendar/Example/ExampleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/03. 6 | // 7 | 8 | import SwiftUI 9 | 10 | #if os(iOS) 11 | struct ExampleView: View { 12 | 13 | @StateObject var manager = CalendarManager() 14 | 15 | var body: some View { 16 | NavigationView{ 17 | VStack(spacing:0){ 18 | JHWeekBar() 19 | JHCalendar(cellHeight:60){ component in 20 | DefaultCalendarCell(component: component) 21 | } 22 | .showTitle(show: false) 23 | .showWeekBar(show: false) 24 | .environmentObject(manager) 25 | Text("Selected day : \(String(manager.selectedComponent.year)) \(String(manager.selectedComponent.month)) \(String(manager.selectedComponent.day))") 26 | Spacer() 27 | } 28 | .navigationBarTitleDisplayMode(.inline) 29 | .toolbar{ 30 | ToolbarItemGroup(placement:.navigationBarTrailing) { 31 | Button(action:{ 32 | withAnimation(.easeInOut(duration: 1)) { 33 | manager.setMode(mode: nil) 34 | } 35 | }){ 36 | Label("Change Mode", systemImage: manager.mode == .Month ? "arrow.down.forward.and.arrow.up.backward" : "arrow.up.backward.and.arrow.down.forward") 37 | .imageScale(.medium) 38 | .foregroundColor(.red) 39 | } 40 | } 41 | ToolbarItem(placement:.navigationBarLeading) { 42 | Text(Calendar.current.monthSymbols[manager.page.current.month - 1]) 43 | .font(.title2) 44 | .fontWeight(.bold) 45 | } 46 | } 47 | } 48 | .navigationViewStyle(.stack) 49 | } 50 | } 51 | 52 | struct ExampleView_Previews: PreviewProvider { 53 | static var previews: some View { 54 | ExampleView() 55 | } 56 | } 57 | #endif 58 | -------------------------------------------------------------------------------- /Sources/JHCalendar/Model/DataModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/11/13. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CalendarComponents : Hashable,Strideable{ 11 | 12 | public var year : Int 13 | public var month : Int 14 | public var day : Int 15 | 16 | public var date : Date { 17 | let comp = DateComponents(year:year,month: month,day: day,hour: 0,minute: 0) 18 | return Calendar.current.date(from: comp) ?? Date() 19 | } 20 | 21 | public func hash(into hasher: inout Hasher) { 22 | hasher.combine(year) 23 | hasher.combine(month) 24 | hasher.combine(day) 25 | } 26 | 27 | public static func == (lhs: CalendarComponents, rhs: CalendarComponents) -> Bool { 28 | return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day 29 | } 30 | 31 | public func advanced(by n: Int) -> CalendarComponents { 32 | let value = self 33 | guard let next = Calendar.current.date(byAdding: .day, value: n, to: value.date) else {return value} 34 | return CalendarComponents(year: next.year, month: next.month, day: next.day) 35 | } 36 | 37 | public func distance(to other: CalendarComponents) -> Int { 38 | let current = self 39 | return Int(other.date.timeIntervalSince(current.date)) 40 | } 41 | } 42 | 43 | extension CalendarComponents { 44 | 45 | /// 1 : Sunday , 2 : Monday , 3 : Tuesday , 4 : Wednesday, 5 : Thursday , 6 : Friday , 7: Saturday 46 | var startWeek : Int { 47 | let monthDate = CalendarComponents(year: year, month: month, day: 1).date 48 | return monthDate.weekday 49 | } 50 | 51 | var endDay : Int { 52 | let monthDate = CalendarComponents(year: year, month: month, day: 1).date 53 | guard let nextMonth = Calendar.current.date(byAdding: .month, value: 1, to: monthDate) 54 | else { return 1 } 55 | guard let lastDate = Calendar.current.date(byAdding: .day, value: -1, to: nextMonth) 56 | else { return 1 } 57 | return lastDate.day 58 | } 59 | } 60 | 61 | public extension CalendarComponents { 62 | 63 | static let startDefault : CalendarComponents = CalendarComponents(year: Date.current.fourYearBefore.year, 64 | month: Date.current.fourYearBefore.month, 65 | day: Date.current.fourYearBefore.day) 66 | static let endDefault : CalendarComponents = CalendarComponents(year: Date.current.fourYearLater.year, 67 | month: Date.current.fourYearLater.month, 68 | day: Date.current.fourYearLater.day) 69 | static let current : CalendarComponents = CalendarComponents(year: Date.current.year, 70 | month: Date.current.month, 71 | day: Date.current.day) 72 | } 73 | 74 | struct DayComponent : Identifiable { 75 | var id : Int 76 | var component : CalendarComponents 77 | } 78 | 79 | public struct CalendarPage { 80 | var monthPage : CalendarComponents 81 | var weekPage : CalendarComponents 82 | var mode : CalendarMode 83 | 84 | public var current : CalendarComponents { 85 | switch mode { 86 | case .Week : 87 | return weekPage 88 | case .Month : 89 | return monthPage 90 | } 91 | } 92 | } 93 | 94 | public enum CalendarMode { 95 | case Month 96 | case Week 97 | } 98 | 99 | public enum CalendarSymbolType { 100 | case veryshort 101 | case short 102 | } 103 | 104 | public enum CalendarDisplayMode { 105 | case page 106 | case scroll 107 | } 108 | -------------------------------------------------------------------------------- /Sources/JHCalendar/Model/DateModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/11/13. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | 12 | var year : Int { 13 | Calendar.current.component(.year, from: self) 14 | } 15 | 16 | var month : Int { 17 | Calendar.current.component(.month, from: self) 18 | } 19 | 20 | var day : Int { 21 | Calendar.current.component(.day, from: self) 22 | } 23 | 24 | /// 1 : 일 , 2 : 월 , 3 : 화 , 4 : 수, 5 : 목 , 6 : 금 , 7: 토 25 | var weekday : Int { 26 | Calendar.current.component(.weekday, from: self) 27 | } 28 | 29 | /// 현재로부터 4년 전 Date값 30 | var fourYearLater : Date { 31 | Calendar.current.date(byAdding: .year, value: 4, to: self) ?? Date() 32 | } 33 | 34 | /// 현재로부터 4년 후 Date값 35 | var fourYearBefore : Date { 36 | Calendar.current.date(byAdding: .year, value: -4, to: self) ?? Date() 37 | } 38 | 39 | static let current = Date() 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/CalendarTitle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CalendarTitle : View { 11 | 12 | var comp : CalendarComponents 13 | 14 | var body: some View { 15 | HStack{ 16 | Text(String(comp.year)) 17 | Text(Calendar.current.monthSymbols[comp.month - 1]) 18 | } 19 | .font(.title2.bold()) 20 | } 21 | } 22 | 23 | struct CalendarTitle_Previews: PreviewProvider { 24 | static var previews: some View { 25 | CalendarTitle(comp: CalendarComponents(year: 2021, month: 12, day: 4)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/DefaultCalendarCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | ///Default DayView 11 | /// 12 | ///If you don't want to define CustomDayView, use DefaultCalendarCell view. 13 | public struct DefaultCalendarCell: View { 14 | @EnvironmentObject var manager : CalendarManager 15 | var component : CalendarComponents 16 | var isSelected : Bool { 17 | manager.selectedComponent == component 18 | } 19 | public var body: some View { 20 | Button(action:{ 21 | withAnimation(.easeInOut) { 22 | manager.selectedComponent = component 23 | } 24 | }){ 25 | Text(String(component.day)) 26 | .foregroundColor(isSelected ? .blue : .accentColor) 27 | .frame(maxWidth:.infinity,maxHeight: .infinity) 28 | } 29 | .buttonStyle(.borderless) 30 | } 31 | } 32 | 33 | struct DefaultCalendarCell_Previews: PreviewProvider { 34 | static var previews: some View { 35 | DefaultCalendarCell(component: CalendarComponents(year: 2021, month: 12, day: 4)) 36 | .environmentObject(CalendarManager()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/JHCalendar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/18. 6 | // 7 | 8 | import SwiftUI 9 | 10 | ///SwiftUI Customizable CalendarView 11 | public struct JHCalendar : View { 12 | 13 | @EnvironmentObject var manager : CalendarManager 14 | @Namespace var transitionID 15 | private var cellHeight : CGFloat 16 | private var content : (CalendarComponents) -> Content 17 | #if os(iOS) 18 | var displayMode : CalendarDisplayMode = .page 19 | #else 20 | let displayMode : CalendarDisplayMode = .scroll 21 | #endif 22 | var customWeekdaySymbols = [String]() 23 | var weekdaySymbolType : CalendarSymbolType = .short 24 | var weekdayBarColor : Color = .primary 25 | var dayPrimaryColor : Color = .primary 26 | var daySecondaryColor : Color = .secondary 27 | var weekdayFont : Font? = nil 28 | var showTitle : Bool = true 29 | var showWeekBar : Bool = true 30 | 31 | ///SwiftUI Customizable CalendarView 32 | /// 33 | ///SwiftUI Customizable CalendarView. You can customzie all components of calendar. 34 | /// 35 | ///JHCalendarView support variety view modifier and options. 36 | /// 37 | ///You can customize Day Content by using your custom view. 38 | ///If you don't want to define CustomDayView, use DefaultCalendarCell. 39 | ///- Parameter cellHeight : DayView height 40 | ///- Parameter content : CustomDayView 41 | public init(cellHeight : CGFloat = 50, 42 | @ViewBuilder content : @escaping (CalendarComponents) -> Content) { 43 | self.cellHeight = cellHeight 44 | self.content = content 45 | } 46 | 47 | #if os(iOS) 48 | var pageMode : some View { 49 | Group{ 50 | switch manager.mode { 51 | case .Month : 52 | TabView(selection: $manager.page.monthPage){ 53 | ForEach(manager.monthComponents){ month in 54 | MonthView(displayMode: .page, month: month.component, 55 | height: cellHeight, 56 | dayPrimaryColor: dayPrimaryColor, 57 | daySecondaryColor: daySecondaryColor, 58 | content: { comp in 59 | content(comp) 60 | }) 61 | .tag(month.component) 62 | } 63 | } 64 | .tabViewStyle(.page(indexDisplayMode: .never)) 65 | .matchedGeometryEffect(id: "View", in: transitionID,anchor: .top) 66 | case .Week : 67 | TabView(selection:$manager.page.weekPage){ 68 | ForEach(manager.weekComponents) { week in 69 | WeekView(week: week.component, 70 | height: cellHeight, 71 | dayPrimaryColor: dayPrimaryColor, 72 | daySecondaryColor: daySecondaryColor, 73 | content: { comp in 74 | content(comp) 75 | }) 76 | .tag(week.component) 77 | } 78 | } 79 | .tabViewStyle(.page(indexDisplayMode: .never)) 80 | .matchedGeometryEffect(id: "View", in: transitionID,anchor: .top) 81 | } 82 | } 83 | .frame(height:manager.mode == .Month ? cellHeight * 6 : cellHeight,alignment: .top) 84 | } 85 | #endif 86 | 87 | var scrollMode : some View { 88 | ScrollViewReader{ proxy in 89 | ScrollView(.vertical,showsIndicators: false){ 90 | LazyVStack{ 91 | ForEach(manager.monthComponents){ month in 92 | VStack{ 93 | MonthView(displayMode: .scroll, 94 | month: month.component, 95 | height: cellHeight, 96 | dayPrimaryColor: dayPrimaryColor, 97 | daySecondaryColor: daySecondaryColor, 98 | content: { comp in 99 | content(comp) 100 | }) 101 | Divider() 102 | }.id(month.component) 103 | } 104 | } 105 | } 106 | .coordinateSpace(name: "ScrollView") 107 | .onAppear{ 108 | proxy.scrollTo(manager.page.monthPage, anchor: .top) 109 | } 110 | } 111 | 112 | } 113 | 114 | public var body: some View { 115 | VStack(spacing:0){ 116 | if showTitle { 117 | CalendarTitle(comp: manager.page.current) 118 | .padding(.bottom) 119 | } 120 | if showWeekBar { 121 | JHWeekBar(weeks: customWeekdaySymbols, 122 | symbolType: weekdaySymbolType, 123 | font: weekdayFont) 124 | .foregroundColor(weekdayBarColor) 125 | } 126 | 127 | #if os(iOS) 128 | switch displayMode { 129 | case .page: 130 | pageMode 131 | case .scroll: 132 | scrollMode 133 | } 134 | #else 135 | scrollMode 136 | #endif 137 | } 138 | } 139 | } 140 | 141 | struct JHCalendar_Previews: PreviewProvider { 142 | static var previews: some View { 143 | JHCalendar{ comp in 144 | DefaultCalendarCell(component: comp) 145 | } 146 | .environmentObject(CalendarManager()) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/Modifier/JHCalendarModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | public extension JHCalendar { 11 | 12 | func customWeekdaySymbols(symbols : [String]) -> JHCalendar { 13 | var view = self 14 | view.customWeekdaySymbols = symbols 15 | return view 16 | } 17 | 18 | func weekdaySymbolType(type : CalendarSymbolType) -> JHCalendar { 19 | var view = self 20 | view.weekdaySymbolType = type 21 | return view 22 | } 23 | 24 | func weekdaySymbolColor(color : Color) -> JHCalendar { 25 | var view = self 26 | view.weekdayBarColor = color 27 | return view 28 | } 29 | 30 | func weekdayFont(font : Font?) -> JHCalendar { 31 | var view = self 32 | view.weekdayFont = font 33 | return view 34 | } 35 | 36 | func calendarCellAccentColor(primary : Color,secondary : Color) -> JHCalendar { 37 | var view = self 38 | view.dayPrimaryColor = primary 39 | view.daySecondaryColor = secondary 40 | return view 41 | } 42 | 43 | func showTitle(show : Bool) -> JHCalendar { 44 | var view = self 45 | view.showTitle = show 46 | return view 47 | } 48 | 49 | func showWeekBar(show : Bool) -> JHCalendar { 50 | var view = self 51 | view.showWeekBar = show 52 | return view 53 | } 54 | 55 | @available(iOS 14.0,*) 56 | func calendarDisplayMode(mode : CalendarDisplayMode) -> JHCalendar { 57 | var view = self 58 | #if os(iOS) 59 | view.displayMode = mode 60 | #endif 61 | return view 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/MonthView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/18. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MonthView : View { 11 | @EnvironmentObject var manager : CalendarManager 12 | 13 | var displayMode : CalendarDisplayMode 14 | let height : CGFloat 15 | var month : CalendarComponents 16 | let columns : [GridItem] 17 | let components : [DayComponent] 18 | let content : (CalendarComponents) -> Content 19 | var dayPrimaryColor : Color 20 | var daySecondaryColor : Color 21 | 22 | init(displayMode : CalendarDisplayMode, 23 | month : CalendarComponents , 24 | height : CGFloat = 50, 25 | dayPrimaryColor : Color = .primary, 26 | daySecondaryColor : Color = .secondary, 27 | @ViewBuilder content : @escaping (CalendarComponents) -> Content) { 28 | let endDay = month.endDay 29 | let startWeek = month.startWeek 30 | let item = GridItem(.flexible(minimum: 10, maximum: .infinity), 31 | spacing: 0, alignment: .center) 32 | var comps = [DayComponent]() 33 | let prDate = Calendar.current.date(byAdding: .month, value: -1, to: month.date) ?? Date() 34 | let prComp = CalendarComponents(year: prDate.year, month: prDate.month, day: 1) 35 | for day in 1 ..< startWeek { 36 | comps.append(DayComponent(id: comps.count, 37 | component: CalendarComponents(year: prComp.year, month: prComp.month, day: prComp.endDay - startWeek + day + 1))) 38 | } 39 | for day in 1 ..< endDay + 1 { 40 | comps.append(DayComponent(id: comps.count, 41 | component: CalendarComponents(year: month.year, month: month.month, day: day))) 42 | } 43 | let count = comps.count 44 | let nxDate = Calendar.current.date(byAdding: .month, value: 1, to: month.date) ?? Date() 45 | for nxDay in 1 ..< 42 - count + 1 { 46 | comps.append(DayComponent(id: comps.count, 47 | component: CalendarComponents(year: nxDate.year, month: nxDate.month, day: nxDay))) 48 | } 49 | self.displayMode = displayMode 50 | self.month = month 51 | self.components = comps 52 | self.columns = Array(repeating: item, count: 7) 53 | self.dayPrimaryColor = dayPrimaryColor 54 | self.daySecondaryColor = daySecondaryColor 55 | self.height = height 56 | self.content = content 57 | } 58 | 59 | var monthView : some View { 60 | LazyVGrid(columns: columns,spacing: 0) { 61 | ForEach(components){ comp in 62 | content(comp.component) 63 | .frame(minHeight:height,maxHeight: .infinity) 64 | .foregroundColor(isEqual(component: comp.component) ? dayPrimaryColor : daySecondaryColor) 65 | .accentColor(isEqual(component: comp.component) ? dayPrimaryColor : daySecondaryColor) 66 | } 67 | } 68 | } 69 | 70 | var body: some View { 71 | switch displayMode { 72 | case .page: 73 | monthView 74 | case .scroll: 75 | GeometryReader{ geometry in 76 | monthView 77 | .onChange(of: geometry.frame(in: .named("ScrollView")).minY , perform: { value in 78 | if value < height * 2 { 79 | DispatchQueue.main.async { 80 | manager.setPage(component: month) 81 | } 82 | } 83 | }) 84 | } 85 | .frame(height:height*6) 86 | } 87 | } 88 | 89 | private func isEqual(component : CalendarComponents) -> Bool { 90 | return month.year == component.year && month.month == component.month 91 | } 92 | } 93 | 94 | 95 | 96 | struct MonthView_Previews: PreviewProvider { 97 | static var previews: some View { 98 | MonthView(displayMode: .page, 99 | month: CalendarComponents(year: 2021, month: 12, day: 4)) { 100 | DefaultCalendarCell(component: $0) 101 | } 102 | .environmentObject(CalendarManager()) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/WeekBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | ///Calendar Week Bar 11 | public struct JHWeekBar : View { 12 | 13 | var weeks : [String] 14 | var symbolType : CalendarSymbolType 15 | var font : Font? 16 | 17 | ///- Parameter weeks : Custom week symbols. 18 | ///- Parameter symbolType : if you not use custom symbol,you can choose system week symbol style (short,very short). 19 | ///- Parameter font : custom Font 20 | public init(weeks : [String] = [], 21 | symbolType : CalendarSymbolType = .short, 22 | font : Font? = nil) { 23 | self.weeks = weeks 24 | self.symbolType = symbolType 25 | self.font = font 26 | } 27 | 28 | public var body: some View { 29 | HStack(spacing:0){ 30 | Group{ 31 | if weeks.count == 7 { 32 | ForEach(weeks,id:\.self) { week in 33 | Text(week) 34 | } 35 | } else { 36 | switch symbolType { 37 | case .veryshort: 38 | ForEach(Calendar.current.veryShortWeekdaySymbols,id : \.self) { week in 39 | Text(week) 40 | } 41 | case .short: 42 | ForEach(Calendar.current.shortWeekdaySymbols,id : \.self) { week in 43 | Text(week) 44 | } 45 | } 46 | } 47 | } 48 | .frame(maxWidth:.infinity) 49 | .font(font) 50 | } 51 | } 52 | } 53 | 54 | struct WeekBar_Previews: PreviewProvider { 55 | static var previews: some View { 56 | JHWeekBar() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/JHCalendar/View/WeekView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUIView.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/12/18. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WeekView : View { 11 | 12 | let height : CGFloat 13 | let week : CalendarComponents 14 | let components : [DayComponent] 15 | let content : (CalendarComponents) -> Content 16 | let columns : [GridItem] 17 | var dayPrimaryColor : Color 18 | var daySecondaryColor : Color 19 | 20 | init(week : CalendarComponents, 21 | height : CGFloat = 50, 22 | dayPrimaryColor : Color = .primary, 23 | daySecondaryColor : Color = .secondary, 24 | @ViewBuilder content : @escaping (CalendarComponents) -> Content) { 25 | var comps = [DayComponent]() 26 | for day in 0 ..< 7 { 27 | let date = Calendar.current.date(byAdding: .day, value: -6 + day, to: week.date) ?? Date() 28 | comps.append(DayComponent(id: comps.count, 29 | component: CalendarComponents(year: date.year, 30 | month: date.month, 31 | day: date.day))) 32 | } 33 | let item = GridItem(.flexible(minimum: 10, maximum: .infinity), 34 | spacing: 0, alignment: .center) 35 | self.columns = Array(repeating: item, count: 7) 36 | self.components = comps 37 | self.dayPrimaryColor = dayPrimaryColor 38 | self.daySecondaryColor = daySecondaryColor 39 | self.week = week 40 | self.content = content 41 | self.height = height 42 | } 43 | 44 | var body: some View { 45 | LazyVGrid(columns:columns){ 46 | ForEach(components) { comp in 47 | content(comp.component) 48 | .frame(minHeight:height,maxHeight: .infinity) 49 | .foregroundColor(isEqual(component: comp.component) ? dayPrimaryColor : daySecondaryColor) 50 | .accentColor(isEqual(component: comp.component) ? dayPrimaryColor : daySecondaryColor) 51 | } 52 | } 53 | } 54 | 55 | private func isEqual(component : CalendarComponents) -> Bool { 56 | return week.year == component.year && week.month == component.month 57 | } 58 | } 59 | 60 | struct WeekView_Previews: PreviewProvider { 61 | static var previews: some View { 62 | WeekView(week: CalendarComponents(year: 2021, month: 12, day: 4)) { comp in 63 | Text(String(comp.day)) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/JHCalendar/ViewModel/CalendarManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Lee Jaeho on 2021/11/13. 6 | // 7 | 8 | import Foundation 9 | 10 | ///CalendarManager 11 | /// 12 | ///This class conform to observableobject. Use this class to manage calendar state (ex. calendar mode, calendar page, calendar day select..) 13 | public class CalendarManager : ObservableObject { 14 | 15 | @Published public var mode : CalendarMode { 16 | willSet(newValue){ 17 | changeMode(newValue: newValue) 18 | } 19 | } 20 | @Published public var page : CalendarPage 21 | @Published public var selectedComponent : CalendarComponents 22 | 23 | let weekComponents : [DayComponent] 24 | let monthComponents : [DayComponent] 25 | let startDate : CalendarComponents 26 | let endDate : CalendarComponents 27 | 28 | ///CalendarManager 29 | /// 30 | ///This class conform to observableobject. Use this class to manage calendar state (ex. calendar mode, calendar page, calendar day select..) 31 | ///- Parameter mode : Calendar mode - Month,Week. 32 | ///In macos, only support Month mode. 33 | ///- Parameter startDate : Calendar first date 34 | ///- Parameter endDate : Calendar last date 35 | ///- Parameter startPoint : Calendar start point when view appears. 36 | public init(mode : CalendarMode = .Month, 37 | startDate : CalendarComponents = .startDefault, 38 | endDate : CalendarComponents = .endDefault, 39 | startPoint : CalendarComponents = .current) { 40 | var comps = [DayComponent]() 41 | 42 | for comp in startDate ..< endDate { 43 | if comp.date.weekday == 7 { 44 | //saturday 45 | comps.append(DayComponent(id: comps.count, 46 | component: comp)) 47 | } 48 | } 49 | 50 | var monthComps = [DayComponent]() 51 | for comp in comps { 52 | if !monthComps.contains(where: {$0.component.year == comp.component.year 53 | && $0.component.month == comp.component.month}) { 54 | monthComps.append(comp) 55 | } 56 | } 57 | 58 | var pg = CalendarPage(monthPage: startPoint, weekPage: startPoint, mode: mode) 59 | 60 | if let monthPage = monthComps.first(where: {$0.component.year == startPoint.year 61 | && $0.component.month == startPoint.month}) ,mode == .Month{ 62 | pg.monthPage = monthPage.component 63 | } else if mode == .Week { 64 | let cWeek = startPoint.date.weekday 65 | let cDate = Calendar.current.date(byAdding: .day, 66 | value: 7-cWeek, 67 | to: startPoint.date) ?? Date() 68 | pg.weekPage = CalendarComponents(year: cDate.year, month: cDate.month, day: cDate.day) 69 | } 70 | 71 | self.startDate = startDate 72 | self.endDate = endDate 73 | self.weekComponents = comps 74 | self.monthComponents = monthComps 75 | self.selectedComponent = startPoint 76 | self.mode = mode 77 | self.page = pg 78 | } 79 | 80 | ///Set CalendarMode 81 | /// 82 | ///If mode parameter is nil, page is automatically changed according to current page value. 83 | ///- Parameter mode : CalendarMode 84 | public func setMode(mode newValue : CalendarMode? = nil) { 85 | if let newValue = newValue { 86 | mode = newValue 87 | } else { 88 | switch mode { 89 | case .Week : 90 | mode = .Month 91 | case .Month : 92 | mode = .Week 93 | } 94 | } 95 | } 96 | 97 | ///Set CalendarPage 98 | /// 99 | ///To change programmatically calendar page, you should use this method. 100 | public func setPage(component : CalendarComponents) { 101 | switch mode { 102 | case .Month: 103 | let currentDate = CalendarComponents(year: component.year, month: component.month, day: 1).date 104 | let newDate = Calendar.current.date(byAdding: .day, value: 7-currentDate.weekday, to: currentDate) ?? Date() 105 | page.monthPage = CalendarComponents(year: newDate.year, month: newDate.month, day: newDate.day) 106 | case .Week: 107 | let week = component.date.weekday 108 | let newDate = Calendar.current.date(byAdding: .day, value: 7-week, to: component.date) ?? Date() 109 | page.weekPage = CalendarComponents(year: newDate.year, month: newDate.month, day: newDate.day) 110 | } 111 | } 112 | 113 | 114 | func changeMode(newValue : CalendarMode) { 115 | switch newValue { 116 | case .Month: 117 | if let newpg = weekComponents.first(where: {$0.component.month == page.weekPage.month 118 | && $0.component.year == page.weekPage.year}) { 119 | page.monthPage = newpg.component 120 | } 121 | case .Week: 122 | if selectedComponent.year == page.monthPage.year 123 | && selectedComponent.month == page.monthPage.month { 124 | // same page 125 | let slweek = selectedComponent.date.weekday 126 | let wkDate = Calendar.current.date(byAdding: .day, value: 7 - slweek, 127 | to: selectedComponent.date) ?? Date() 128 | page.weekPage = CalendarComponents(year: wkDate.year, month: wkDate.month, day: wkDate.day) 129 | } else { 130 | if let newpg = weekComponents.first(where: {$0.component.month == page.monthPage.month 131 | && $0.component.year == page.monthPage.year}) { 132 | page.weekPage = newpg.component 133 | } 134 | } 135 | } 136 | page.mode = newValue 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Tests/JHCalendarTests/JHCalendarTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import JHCalendar 3 | 4 | final class JHCalendarTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | //XCTAssertEqual(JHCalendar().text, "Hello, World!") 10 | } 11 | } 12 | --------------------------------------------------------------------------------