├── .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 | 
6 | 
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 | ||||
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 |
--------------------------------------------------------------------------------