Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/GGJJack/SwiftUICalendar
Simple calendar for SwiftUI
https://github.com/GGJJack/SwiftUICalendar
calendar cocoapods ios swift swiftpackagemanager swiftui swiftuicalendar
Last synced: 3 months ago
JSON representation
Simple calendar for SwiftUI
- Host: GitHub
- URL: https://github.com/GGJJack/SwiftUICalendar
- Owner: GGJJack
- License: mit
- Created: 2021-10-26T02:38:08.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2023-04-29T04:13:29.000Z (over 1 year ago)
- Last Synced: 2024-03-14T22:36:00.489Z (8 months ago)
- Topics: calendar, cocoapods, ios, swift, swiftpackagemanager, swiftui, swiftuicalendar
- Language: Swift
- Homepage:
- Size: 1.41 MB
- Stars: 163
- Watchers: 4
- Forks: 28
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-swiftui-libraries - SwiftUICalendar - SwiftUI base simple Calendar (Calendar / Content)
README
# SwiftUICalendar
[![Version](https://img.shields.io/cocoapods/v/SwiftUICalendar.svg?style=flat)](https://cocoapods.org/pods/SwiftUICalendar)
[![License](https://img.shields.io/cocoapods/l/SwiftUICalendar.svg?style=flat)](https://cocoapods.org/pods/SwiftUICalendar)
[![Platform](https://img.shields.io/cocoapods/p/SwiftUICalendar.svg?style=flat)](https://cocoapods.org/pods/SwiftUICalendar)SwiftUICalendar is calendar view for SwiftUI
## Installation
### [Swift Package Manager](https://swift.org/package-manager/)
- From url : `https://github.com/GGJJack/SwiftUICalendar`
or
- Package.swift
```swift
.package(name: "SwiftUICalendar", url: "https://github.com/GGJJack/SwiftUICalendar", from: "0.1.14")
```### [CocoaPods](https://cocoapods.org)
```ruby
pod 'SwiftUICalendar'
```### import
```swift
import SwiftUICalendar
```## Features
- Infinite scroll
- Support horizontal and vertical scroll
- Full custom calendar cell
- Pager lock
- Starting the week with Sunday or Monday## Example
### Basic
```swift
CalendarView() { date in
Text("\(date.day)")
}
```### Basic use
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/basic_use.gif?raw=true)
Show example code
```swift
struct BasicUseView: View {
@ObservedObject var controller: CalendarController = CalendarController(orientation: .vertical)var body: some View {
GeometryReader { reader in
VStack(alignment: .center, spacing: 0) {
Text("\(controller.yearMonth.monthShortString), \(String(controller.yearMonth.year))")
.font(.title)
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
HStack(alignment: .center, spacing: 0) {
ForEach(0..<7, id: \.self) { i in
Text(DateFormatter().shortWeekdaySymbols[i])
.font(.headline)
.frame(width: reader.size.width / 7)
}
}
CalendarView(controller) { date in
GeometryReader { geometry in
ZStack(alignment: .center) {
if date.isToday {
Circle()
.padding(4)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.foregroundColor(.orange)
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .bold, design: .default))
.foregroundColor(.white)
} else {
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.foregroundColor(getColor(date))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
}
}
}
}
}
}
```### Calendar scroll
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/calendar_scroll.gif?raw=true)
Show example code
```swift
struct CalendarScrollView: View {
@ObservedObject var controller: CalendarController = CalendarController()var body: some View {
GeometryReader { reader in
VStack(alignment: .center, spacing: 0) {
HStack(alignment: .center, spacing: 0) {
Spacer()
Button("Older") {
controller.scrollTo(YearMonth(year: 1500, month: 1), isAnimate: true)
}
Spacer()
Button("Today") {
controller.scrollTo(YearMonth.current, isAnimate: false)
}
Spacer()
Button("Today Scroll") {
controller.scrollTo(YearMonth.current, isAnimate: true)
}
Spacer()
Button("Future") {
controller.scrollTo(YearMonth(year: 2500, month: 1), isAnimate: true)
}
Spacer()
}
Text("\(controller.yearMonth.monthShortString), \(String(controller.yearMonth.year))")
.font(.title)
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
HStack(alignment: .center, spacing: 0) {
ForEach(0..<7, id: \.self) { i in
Text(DateFormatter().shortWeekdaySymbols[i])
.font(.headline)
.frame(width: reader.size.width / 7)
}
}
CalendarView(controller) { date in
GeometryReader { geometry in
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
.navigationBarTitle("Calendar Scroll")
// .onChange(of: controller.yearMonth) { yearMonth in // If you want to detect date change
// print(yearMonth)
// }
}
}
}
}
```### Embed Header
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/embed_header.gif?raw=true)
Show example code
```swift
struct EmbedHeaderView: View {
@ObservedObject var controller: CalendarController = CalendarController()
var body: some View {
GeometryReader { reader in
VStack {
HStack(alignment: .center, spacing: 0) {
Button("Prev") {
controller.scrollTo(controller.yearMonth.addMonth(value: -1), isAnimate: true)
}
.padding(8)
Spacer()
Text("\(controller.yearMonth.monthShortString), \(String(controller.yearMonth.year))")
.font(.title)
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
Spacer()
Button("Next") {
controller.scrollTo(controller.yearMonth.addMonth(value: 1), isAnimate: true)
}
.padding(8)
}
CalendarView(controller, header: { week in
GeometryReader { geometry in
Text(week.shortString)
.font(.subheadline)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
}
}, component: { date in
GeometryReader { geometry in
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
})
}
}
.navigationBarTitle("Embed header")
}
}
```### Information
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/information.gif?raw=true)
Show example code
```swift
extension YearMonthDay: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.year)
hasher.combine(self.month)
hasher.combine(self.day)
}
}struct InformationView: View {
var informations = [YearMonthDay: [(String, Color)]]()init() {
var date = YearMonthDay.current
informations[date] = []
informations[date]?.append(("Hello", Color.orange))
informations[date]?.append(("World", Color.blue))date = date.addDay(value: 3)
informations[date] = []
informations[date]?.append(("Test", Color.pink))date = date.addDay(value: 8)
informations[date] = []
informations[date]?.append(("Jack", Color.green))date = date.addDay(value: 5)
informations[date] = []
informations[date]?.append(("Home", Color.red))date = date.addDay(value: -23)
informations[date] = []
informations[date]?.append(("Meet at 8, Home", Color.purple))date = date.addDay(value: -5)
informations[date] = []
informations[date]?.append(("Home", Color.yellow))date = date.addDay(value: -10)
informations[date] = []
informations[date]?.append(("Baseball", Color.green))
}var body: some View {
GeometryReader { reader in
VStack {
CalendarView(header: { week in
GeometryReader { geometry in
Text(week.shortString)
.font(.subheadline)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
}
}, component: { date in
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 2) {
if date.isToday {
Text("\(date.day)")
.font(.system(size: 10, weight: .bold, design: .default))
.padding(4)
.foregroundColor(.white)
.background(Color.red.opacity(0.95))
.cornerRadius(14)
} else {
Text("\(date.day)")
.font(.system(size: 10, weight: .light, design: .default))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
.foregroundColor(getColor(date))
.padding(4)
}
if let infos = informations[date] {
ForEach(infos.indices) { index in
let info = infos[index]
Text(info.0)
.lineLimit(1)
.foregroundColor(.white)
.font(.system(size: 8, weight: .bold, design: .default))
.padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4))
.frame(width: geometry.size.width, alignment: .center)
.background(info.1.opacity(0.75))
.cornerRadius(4)
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
}
})
}
}
.navigationBarTitle("Information")
}private func getColor(_ date: YearMonthDay) -> Color {
if date.dayOfWeek == .sun {
return Color.red
} else if date.dayOfWeek == .sat {
return Color.blue
} else {
return Color.black
}
}
}
```### Selection
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/selection.gif?raw=true)
Show example code
```swift
struct SelectionView: View {
@ObservedObject var controller: CalendarController = CalendarController()
@State var focusDate: YearMonthDay? = YearMonthDay.currentvar body: some View {
GeometryReader { reader in
VStack {
CalendarView(controller, header: { week in
GeometryReader { geometry in
Text(week.shortString)
.font(.subheadline)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
}
}, component: { date in
GeometryReader { geometry in
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
.border(.green.opacity(0.8), width: (focusDate == date ? 1 : 0))
.cornerRadius(2)
.contentShape(Rectangle())
.onTapGesture {
focusDate = (date != focusDate ? date : nil)
}
}
})
}
}
.navigationBarTitle("Selection")
}
}
```### Information + Selection
![Basic Use](https://github.com/GGJJack/SwiftUICalendar/blob/master/img/info_with_select.gif?raw=true)
Show example code
```swift
struct InformationWithSelectionView: View {
let controller = CalendarController()
var informations = [YearMonthDay: [(String, Color)]]()
@State var focusDate: YearMonthDay? = nil
@State var focusInfo: [(String, Color)]? = nilinit() {
var date = YearMonthDay.current
informations[date] = []
informations[date]?.append(("Hello", Color.orange))
informations[date]?.append(("World", Color.blue))date = date.addDay(value: 3)
informations[date] = []
informations[date]?.append(("Test", Color.pink))date = date.addDay(value: 8)
informations[date] = []
informations[date]?.append(("Jack", Color.green))date = date.addDay(value: 5)
informations[date] = []
informations[date]?.append(("Home", Color.red))date = date.addDay(value: -23)
informations[date] = []
informations[date]?.append(("Meet at 8, Home", Color.purple))date = date.addDay(value: -5)
informations[date] = []
informations[date]?.append(("Home", Color.yellow))date = date.addDay(value: -10)
informations[date] = []
informations[date]?.append(("Baseball", Color.green))
}var body: some View {
GeometryReader { reader in
VStack {
CalendarView(controller, header: { week in
GeometryReader { geometry in
Text(week.shortString)
.font(.subheadline)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
}
}, component: { date in
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 2) {
if date.isToday {
Text("\(date.day)")
.font(.system(size: 10, weight: .bold, design: .default))
.padding(4)
.foregroundColor(.white)
.background(Color.red.opacity(0.95))
.cornerRadius(14)
} else {
Text("\(date.day)")
.font(.system(size: 10, weight: .light, design: .default))
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
.foregroundColor(getColor(date))
.padding(4)
}
if let infos = informations[date] {
ForEach(infos.indices) { index in
let info = infos[index]
if focusInfo != nil {
Rectangle()
.fill(info.1.opacity(0.75))
.frame(width: geometry.size.width, height: 4, alignment: .center)
.cornerRadius(2)
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
} else {
Text(info.0)
.lineLimit(1)
.foregroundColor(.white)
.font(.system(size: 8, weight: .bold, design: .default))
.padding(EdgeInsets(top: 2, leading: 4, bottom: 2, trailing: 4))
.frame(width: geometry.size.width, alignment: .center)
.background(info.1.opacity(0.75))
.cornerRadius(4)
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
}
}
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading)
.border(.green.opacity(0.8), width: (focusDate == date ? 1 : 0))
.cornerRadius(2)
.contentShape(Rectangle())
.onTapGesture {
withAnimation {
if focusDate == date {
focusDate = nil
focusInfo = nil
} else {
focusDate = date
focusInfo = informations[date]
}
}
}
}
})
if let infos = focusInfo {
List(infos.indices, id: \.self) { index in
let info = infos[index]
HStack(alignment: .center, spacing: 0) {
Circle()
.fill(info.1.opacity(0.75))
.frame(width: 12, height: 12)
Text(info.0)
.padding(.leading, 8)
}
}
.frame(width: reader.size.width, height: 160, alignment: .center)
}
}
}
.navigationBarTitle("Info + Select")
}private func getColor(_ date: YearMonthDay) -> Color {
if date.dayOfWeek == .sun {
return Color.red
} else if date.dayOfWeek == .sat {
return Color.blue
} else {
return Color.black
}
}
}```
### Date change detection
```Swift
CalendarView(controller) { date in
....
}
.onChange(of: controller.yearMonth) { yearMonth in
print(yearMonth)
}
```### Start with Monday
![Basic Use](./img/start_monday.gif?raw=true)
Show example code
```swift
struct StartMondayView: View {
@ObservedObject var controller: CalendarController = CalendarController()var body: some View {
GeometryReader { reader in
VStack {
Text("\(controller.yearMonth.monthShortString), \(String(controller.yearMonth.year))")
.font(.title)
.padding(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))CalendarView(controller, startWithMonday: true, headerSize: .fixHeight(50.0)) { week in
Text("\(week.shortString)")
.font(.headline)
.frame(width: reader.size.width / 7)
} component: { date in
GeometryReader { geometry in
if date.isToday {
Circle()
.padding(4)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.foregroundColor(.orange)
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .bold, design: .default))
.foregroundColor(.white)
} else {
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.foregroundColor(.black)
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
}
}
}
}
}
```Show example code
```swift
struct StartMondayView: View {
@ObservedObject var controller: CalendarController = CalendarController()var body: some View {
GeometryReader { reader in
VStack {
HStack(alignment: .center, spacing: 0) {
ForEach(0..<7, id: \.self) { i in
Text(DateFormatter().shortWeekdaySymbols[i < 6 ? i + 1 : 0])
.font(.headline)
.frame(width: reader.size.width / 7)
}
}
CalendarView(controller, startWithMonday: true, headerSize: .fixHeight(50.0)) { week in
Text("\(week.shortString)")
.font(.headline)
.frame(width: reader.size.width / 7)
} component: { date in
GeometryReader { geometry in
if date.isToday {
Circle()
.padding(4)
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.foregroundColor(.orange)
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .bold, design: .default))
.foregroundColor(.white)
} else {
Text("\(date.day)")
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .center)
.font(.system(size: 10, weight: .light, design: .default))
.foregroundColor(.black)
.opacity(date.isFocusYearMonth == true ? 1 : 0.4)
}
}
}
}
}
}
}
```## Struct
### CalendarView
```Swift
public struct CalendarView: View {
public init(
_ controller: CalendarController = CalendarController(),
startWithMonday: Bool = false,
@ViewBuilder component: @escaping (YearMonthDay) -> CalendarCell
) {
...
}
public init(
_ controller: CalendarController = CalendarController(),
startWithMonday: Bool = false,
headerSize: HeaderSize = .fixHeight(40),
@ViewBuilder header: @escaping (Week) -> HeaderCell,
@ViewBuilder component: @escaping (YearMonthDay) -> CalendarCell
) {
...
}...
}
```### HeaderSize
```Swift
public enum HeaderSize {
case zero
case ratio
case fixHeight(CGFloat)
}
```### CalendarController
```Swift
public class CalendarController: ObservableObject {
public init(
_ yearMonth: YearMonth = .current,
orientation: Orientation = .horizontal,
isLocked: Bool = false
)...
}var verticalController = CalendarController(
YearMonth.current,
orientation: .vertical,
isLocked: true
)var controller = CalendarController()
// Scroll with animate
controller.scrollTo(year: 1991, month: 2, isAnimate: true)// Scroll without animate
controller.scrollTo(YearMonth.current, isAnimate: false)// Lock Pager
controller.isLocked = true```
### YearMonth
```Swift
public struct YearMonth: Equatable {
public let year: Int
public let month: Intpublic init(year: Int, month: Int) { ... }
...
}let date = YearMonth(year: 2021, month: 10)
let now = YearMonth.current // Nowprint(date.monthShortString) // Oct // Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
let nextMonth = date.addMonth(value: 1) // {year: 2021, month: 11}
let diff = nextMonth.diffMonth(value: date) // 2021/11 - 2021/10 = 1
let components: DateComponents = nextMonth.toDateComponents()
```
### YearMonthDay
```Swift
public struct YearMonthDay: Equatable {
public let year: Int
public let month: Int
public let day: Int
public let isFocusYearMonth: Bool?public init(year: Int, month: Int, day: Int) { ... }
public init(year: Int, month: Int, day: Int, isFocusYearMonth: Bool) { ... }
...
}let date = YearMonthDay(year: 2021, month: 10, day: 26)
let now = YearMonthDay.currentlet isToday = now.isToday // true
let dayOfWeek: Week = date.dayOfWeek // Tue // Sun, Mon, Tue, Wed, Thu, Fri, Sat
let toDate = date.date! // { 2021/10/26 }
let components: DateComponents = date.toDateComponents()
let tomorrow = date.addDay(value: 1) // {year: 2021, month: 10, day: 27}
let diff = tomorrow.diffDay(value: date) // 2021/10/27 - 2021/10/26 = 1
```### Week
```Swift
public enum Week: Int, CaseIterable {
case sun = 0
case mon = 1
case tue = 2
case wed = 3
case thu = 4
case fri = 5
case sat = 6public var shortString: String // Sun, Mon, Tue, Wed, Thu, Fri, Sat
}
```## Contributors
Thank you very much for your contribution! 🙏
| [](https://github.com/aaron25mt) |[](https://github.com/NSRover) | [](https://github.com/sanhee16) |
|:---:|:---:|:---:|
| [@aaron25mt](https://github.com/aaron25mt) | [@NSRover](https://github.com/NSRover) |[@sanhee16](https://github.com/sanhee16) |## Author
GGJJack, [email protected]
## License
SwiftUICalendar is available under the MIT license. See the LICENSE file for more info.