├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Images
├── Dark.png
└── Light.png
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── MonthCal
│ ├── Model
│ ├── Colors.swift
│ ├── Day.swift
│ └── Month.swift
│ ├── Utility
│ ├── GridStack.swift
│ └── Helper.swift
│ └── ViewModel
│ ├── CalendarView.swift
│ ├── DayCellView.swift
│ ├── File.swift
│ ├── MonthView.swift
│ └── WeekdaysView.swift
└── Tests
├── LinuxMain.swift
└── MonthCalTests
├── MonthCalTests.swift
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Images/Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeslo/MonthCal/f93ad08d62f4333ee6894a52218b98269bc2070c/Images/Dark.png
--------------------------------------------------------------------------------
/Images/Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akeslo/MonthCal/f93ad08d62f4333ee6894a52218b98269bc2070c/Images/Light.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 akeslo
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.1
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: "MonthCal",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "MonthCal",
12 | targets: ["MonthCal"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "MonthCal",
23 | dependencies: []),
24 | .testTarget(
25 | name: "MonthCalTests",
26 | dependencies: ["MonthCal"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MonthCal
2 | **A SwiftUI Calendar Generator for IOS**
3 |
4 | ## Features:
5 | - Supply starting date
6 | - Select months to display
7 | - Disabled, Enabled, Selected, Today day states
8 | - Color control
9 | - Dark Mode Support
10 |
11 | ## Photos
12 | 
13 | 
14 |
15 | ## Installation
16 | - Requires IOS 13.0+ / Xcode 11+ / Swift 5.1+
17 |
18 | ### Option 1
19 | - Copy `Sources/MonthCal` files into your xcode project
20 |
21 | ### Option 2
22 | - Add via Swift Package Manager
23 |
24 | ### Configure
25 | - To customize the colors used, edit colors listed in the `Colors.swift` file
26 |
27 | ### Usage
28 | - Show calendar with a start date and amount of months to display
29 | ```
30 | Import MonthCal
31 | CalendarView(start: Date(), monthsToShow: 6)
32 | ```
33 |
34 | - Disable ability for dates to be selected
35 | ```
36 | Import MonthCal
37 | CalendarView(start: Date(), monthsToShow: 6, daysSelectable: false)
38 | ```
39 |
40 | ## License
41 | - MonthCal is available under the MIT license. See the `LICENSE` file for more info.
42 |
43 | ## Shoutouts
44 | - Paul Hudson - [@twostraws](https://twitter.com/twostraws) - https://www.hackingwithswift.com/100/swiftui
45 | - RaffiKian - https://github.com/RaffiKian/RKCalendar - Inspiration
46 |
--------------------------------------------------------------------------------
/Sources/MonthCal/Model/Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Colors.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/13/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | @available(OSX 10.15, *)
13 | @available(iOS 13.0, *)
14 | class Colors: ObservableObject {
15 |
16 | //Foreground
17 | @Published var textColor: Color = Color.primary
18 | @Published var todayColor: Color = Color.blue
19 | @Published var disabledColor: Color = Color.gray
20 | @Published var selectedColor: Color = Color.primary
21 |
22 | //Background
23 | @Published var backgroundColor: Color = Color.clear
24 | @Published var weekdayBackgroundColor: Color = Color.clear
25 | @Published var selectedBackgroundColor: Color = Color.orange
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/MonthCal/Model/Day.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Day.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/12/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | @available(OSX 10.15, *)
13 | @available(iOS 13.0, *)
14 | class Day: ObservableObject {
15 |
16 | @Published var isSelected = false
17 |
18 | var selectableDays: Bool
19 | var dayDate: Date
20 | var dayName: String {
21 | dayDate.dateToString(format: "d")
22 | }
23 | var isToday = false
24 | var disabled = false
25 | let colors = Colors()
26 | var textColor: Color {
27 | if isSelected {
28 | return colors.selectedColor
29 | } else if isToday {
30 | return colors.todayColor
31 | } else if disabled {
32 | return colors.disabledColor
33 | }
34 | return colors.textColor
35 | }
36 | var backgroundColor: Color {
37 | if isSelected {
38 | return colors.selectedBackgroundColor
39 | } else {
40 | return colors.backgroundColor
41 | }
42 | }
43 |
44 | init(date: Date, today: Bool = false, disable: Bool = false, selectable: Bool = true) {
45 | dayDate = date
46 | isToday = today
47 | disabled = disable
48 | selectableDays = selectable
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/MonthCal/Model/Month.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Month.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/12/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | struct Month {
14 |
15 | private let calendar = Calendar.current
16 |
17 | var startDate: Date
18 | var selectableDays: Bool
19 | var today = Date()
20 | var monthNameYear: String {
21 | self.monthHeader()
22 | }
23 | var monthDays: [Int: [Day]] {
24 | return self.daysArray()
25 | }
26 | var monthRows: Int {
27 | self.rows()
28 | }
29 |
30 |
31 | private func monthHeader() -> String {
32 | let components = calendar.dateComponents([.year, .month], from: startDate)
33 | let currentMonth = calendar.date(from: components)!
34 | return currentMonth.dateToString(format: "LLLL yyyy")
35 | }
36 |
37 | private func dateToString(date: Date, format: String) -> String {
38 | let dateFormat = DateFormatter.init()
39 | dateFormat.dateFormat = format
40 | let dateString = dateFormat.string(from: date)
41 | return dateString
42 | }
43 |
44 | private func firstOfMonth() -> Date {
45 | let components = calendar.dateComponents([.year, .month], from: startDate)
46 | let firstOfMonth = calendar.date(from: components)!
47 | return firstOfMonth
48 | }
49 |
50 | private func lastOfMonth() -> Date {
51 | var components = DateComponents()
52 | components.month = 1
53 | components.day = -1
54 | let lastOfMonth = calendar.date(byAdding: components, to: firstOfMonth())!
55 | return lastOfMonth
56 | }
57 |
58 | private func dateToWeekday(date: Date) -> Int {
59 | let components = calendar.dateComponents([.weekday], from: date)
60 | guard let weekday = components.weekday else {
61 | fatalError("Cannot convert weekday to Int")
62 | }
63 | return weekday
64 | }
65 |
66 | private func rows() -> Int {
67 | let columns = monthDays.count
68 | var rowCount = 1
69 | for col in 1...columns {
70 | if monthDays[col]!.count > rowCount {
71 | rowCount = monthDays[col]!.count
72 | }
73 | }
74 | return rowCount
75 | }
76 |
77 | private func daysArray() -> [Int: [Day]] {
78 | var arrayOfDays = [
79 | 1: [Day](),
80 | 2: [Day](),
81 | 3: [Day](),
82 | 4: [Day](),
83 | 5: [Day](),
84 | 6: [Day](),
85 | 7: [Day]()
86 | ]
87 | let fom = firstOfMonth()
88 | let lom = lastOfMonth()
89 | var currentDate = fom
90 |
91 | while (fom <= currentDate && currentDate <= lom) {
92 | let weekday = dateToWeekday(date: currentDate)
93 | let disabled = currentDate > today ? true : false
94 | let currentDateInt = Int(currentDate.dateToString(format: "MMdyy"))!
95 | let todayDateInt = Int(today.dateToString(format: "MMdyy"))!
96 | let isToday = currentDateInt == todayDateInt ? true : false
97 | let currentDay = Day(date: currentDate, today: isToday, disable: disabled, selectable: selectableDays)
98 | arrayOfDays[weekday]?.append(currentDay)
99 |
100 | if fom == currentDate {
101 | var startDay = weekday - 1
102 | while startDay > 0 {
103 | arrayOfDays[startDay]?.append(Day(date: Date(timeIntervalSince1970: 0)))
104 | startDay -= 1
105 | }
106 | }
107 |
108 | if lom == currentDate {
109 | var endDay = weekday + 1
110 | while endDay <= 7 {
111 | arrayOfDays[endDay]?.append(Day(date: Date(timeIntervalSince1970: 0)))
112 | endDay += 1
113 | }
114 | }
115 |
116 |
117 | //Increment date
118 | var components = calendar.dateComponents([.day], from: currentDate)
119 | components.day = +1
120 | currentDate = calendar.date(byAdding: components, to: currentDate)!
121 | }
122 |
123 | return arrayOfDays
124 | }
125 |
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/Sources/MonthCal/Utility/GridStack.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridStack.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/11/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | /* Credit: Paul Hudson, https://www.hackingwithswift.com/quick-start/swiftui/how-to-position-views-in-a-grid
8 | */
9 | import SwiftUI
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | struct GridStack: View {
14 |
15 | let rows: Int
16 | let cols: Int
17 | let content: (Int, Int) -> Content
18 |
19 | var body: some View {
20 |
21 | VStack {
22 | ForEach(0.. Content) {
39 | self.rows = rows
40 | self.cols = columns
41 | self.content = content
42 | }
43 |
44 | }
45 |
46 | @available(OSX 10.15, *)
47 | @available(iOS 13.0, *)
48 | struct GridStack_Previews: PreviewProvider {
49 |
50 | static var previews: some View {
51 | GridStack(rows: 5, columns: 7) { row, col in
52 | Text("1")
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/MonthCal/Utility/Helper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Helper.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/12/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Date {
12 |
13 | func dateToString(format: String) -> String {
14 | let dateFormat = DateFormatter.init()
15 | dateFormat.dateFormat = format
16 | let dateString = dateFormat.string(from: self)
17 | return dateString
18 | }
19 | }
20 |
21 |
22 | extension String {
23 |
24 | func stringToDate(format: String) -> Date {
25 | let dateFormat = DateFormatter.init()
26 | dateFormat.dateFormat = format
27 | let date = dateFormat.date(from: self)!
28 | return date
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/MonthCal/ViewModel/CalendarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CalendarView.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/13/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | public struct CalendarView: View {
14 |
15 | let startDate: Date
16 | let monthsToDisplay: Int
17 | var selectableDays = true
18 |
19 |
20 | public init(start: Date, monthsToShow: Int, daysSelectable: Bool = true) {
21 | self.startDate = start
22 | self.monthsToDisplay = monthsToShow
23 | self.selectableDays = daysSelectable
24 |
25 | }
26 |
27 | public var body: some View {
28 | VStack {
29 | WeekdaysView()
30 | ScrollView {
31 | MonthView(month: Month(startDate: startDate, selectableDays: selectableDays))
32 | if monthsToDisplay > 1 {
33 | ForEach(1.. Date {
42 | var components = DateComponents()
43 | components.month = add
44 | let next = Calendar.current.date(byAdding: components, to: currentMonth)!
45 | return next
46 | }
47 |
48 |
49 | }
50 |
51 | @available(OSX 10.15, *)
52 | @available(iOS 13.0, *)
53 | struct CalendarView_Previews: PreviewProvider {
54 | static var previews: some View {
55 | CalendarView(start: Date(), monthsToShow: 2)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Sources/MonthCal/ViewModel/DayCellView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CellView.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/13/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | struct DayCellView: View {
14 |
15 | @ObservedObject var day: Day
16 |
17 | var body: some View {
18 | Text(day.dayName).frame(width: 32, height: 32)
19 | .foregroundColor(day.textColor)
20 | .background(day.backgroundColor)
21 | .clipShape(RoundedRectangle(cornerRadius: 10))
22 | .clipped()
23 | .onTapGesture {
24 | if self.day.disabled == false && self.day.selectableDays {
25 | self.day.isSelected.toggle()
26 | }
27 | }
28 | }
29 | }
30 |
31 | @available(OSX 10.15, *)
32 | @available(iOS 13.0, *)
33 | struct CellView_Previews: PreviewProvider {
34 | static var previews: some View {
35 | DayCellView(day: Day(date: Date()))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/MonthCal/ViewModel/File.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Adam Kes on 11/13/19.
6 | //
7 |
8 | import Foundation
9 |
--------------------------------------------------------------------------------
/Sources/MonthCal/ViewModel/MonthView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MonthView.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/12/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | struct MonthView: View {
14 |
15 |
16 | var month: Month
17 |
18 | var body: some View {
19 | VStack {
20 | Text("\(month.monthNameYear)")
21 | GridStack(rows: month.monthRows, columns: month.monthDays.count) { row, col in
22 | if self.month.monthDays[col+1]![row].dayDate == Date(timeIntervalSince1970: 0) {
23 | Text("").frame(width: 32, height: 32)
24 | } else {
25 | DayCellView(day: self.month.monthDays[col+1]![row])
26 | }
27 |
28 | }
29 | }
30 | .padding(.bottom, 20)
31 |
32 | }
33 | }
34 | @available(OSX 10.15, *)
35 | @available(iOS 13.0, *)
36 | struct MonthView_Previews: PreviewProvider {
37 | static var previews: some View {
38 | MonthView(month: Month(startDate: Date(), selectableDays: true))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/MonthCal/ViewModel/WeekdaysView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Weekdays.swift
3 | // CalSwiftUI
4 | //
5 | // Created by Adam Kes on 11/13/19.
6 | // Copyright © 2019 KesDev. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | @available(OSX 10.15, *)
12 | @available(iOS 13.0, *)
13 | struct WeekdaysView: View {
14 | let weekdays = ["Sun", "Mon", "Tue", "Wen", "Thu", "Fri", "Sat"]
15 | let colors = Colors()
16 |
17 | var body: some View {
18 | HStack {
19 | GridStack(rows: 1, columns: 7) { row, col in
20 | Text(self.weekdays[col])
21 | }
22 | }.padding(.bottom, 20).background(colors.weekdayBackgroundColor)
23 | }
24 | }
25 |
26 | @available(OSX 10.15, *)
27 | @available(iOS 13.0, *)
28 | struct WeekdaysView_Previews: PreviewProvider {
29 | static var previews: some View {
30 | WeekdaysView()
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import MonthCalTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += MonthCalTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/MonthCalTests/MonthCalTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import MonthCal
3 |
4 | final class MonthCalTests: XCTestCase {
5 | func testExample() {
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 |
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/MonthCalTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(MonthCalTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------