I've been playing around with FSCalendar and it's helped me build my own customized calendar.
Because it's written in UIKit, I've had a couple of problems integrating it to my SwiftUI project, such as adding a Next and Previous button to the sides of the calendar.
This is what I have so far:
ContentView, where I used an HStack to add the buttons to the sides of my calendar
struct ContentView: View {
let myCalendar = MyCalendar()
var body: some View {
HStack(spacing: 5) {
Button(action: {
myCalendar.previousTapped()
}) { Image("back-arrow") }
MyCalendar()
Button(action: {
myCalendar.nextTapped()
}) { Image("next-arrow") }
}
}}
And the MyCalendar struct which, in order to integrate the FSCalendar library, is a UIViewRepresentable. This is also where I added the two functions (nextTapped and previousTapped) which should change the displayed month when the Buttons are tapped:
struct MyCalendar: UIViewRepresentable {
let calendar = FSCalendar(frame: CGRect(x: 0, y: 0, width: 320, height: 300))
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> FSCalendar {
calendar.delegate = context.coordinator
calendar.dataSource = context.coordinator
return calendar
}
func updateUIView(_ uiView: FSCalendar, context: Context) {
}
func nextTapped() {
let nextMonth = Calendar.current.date(byAdding: .month, value: 1, to: calendar.currentPage)
calendar.setCurrentPage(nextMonth!, animated: true)
print(calendar.currentPage)
}
func previousTapped() {
let previousMonth = Calendar.current.date(byAdding: .month, value: -1, to: calendar.currentPage)
calendar.setCurrentPage(previousMonth!, animated: true)
print(calendar.currentPage)
}
class Coordinator: NSObject, FSCalendarDelegateAppearance, FSCalendarDataSource, FSCalendarDelegate {
var parent: MyCalendar
init(_ calendar: MyCalendar) {
self.parent = calendar
}
func minimumDate(for calendar: FSCalendar) -> Date {
return Date()
}
func maximumDate(for calendar: FSCalendar) -> Date {
return Date().addingTimeInterval((60 * 60 * 24) * 365)
}
}}
This is what it looks like in the simulator:
As you can see, I've managed to print the currentPage in the terminal whenever the next or previous buttons are tapped, but the currentPage is not changing in the actual calendar. How could I fix this?
As you are using UIViewRepresentable
protocol for bind UIView class with SwiftUI. Here you have to use ObservableObject - type of object with a publisher that emits before the object has changed.
You can check the code below for the resulting output: (Edit / Improvement most welcomed)
import SwiftUI
import UIKit
import FSCalendar
class CalendarData: ObservableObject{
@Published var selectedDate : Date = Date()
@Published var titleOfMonth : Date = Date()
@Published var crntPage: Date = Date()
}
struct ContentView: View {
@ObservedObject private var calendarData = CalendarData()
var strDateSelected: String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale.current
return dateFormatter.string(from: calendarData.selectedDate)
}
var strMonthTitle: String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM yyyy"
dateFormatter.locale = Locale.current
return dateFormatter.string(from: calendarData.titleOfMonth)
}
var body: some View {
VStack {
HStack(spacing: 100) {
Button(action: {
self.calendarData.crntPage = Calendar.current.date(byAdding: .month, value: -1, to: self.calendarData.crntPage)!
}) { Image(systemName: "arrow.left") }
.frame(width: 35, height: 35, alignment: .leading)
Text(strMonthTitle)
.font(.headline)
Button(action: {
self.calendarData.crntPage = Calendar.current.date(byAdding: .month, value: 1, to: self.calendarData.crntPage)!
}) { Image(systemName: "arrow.right") }
.frame(width: 35, height: 35, alignment: .trailing)
}
CustomCalendar(dateSelected: $calendarData.selectedDate, mnthNm: $calendarData.titleOfMonth, pageCurrent: $calendarData.crntPage)
.padding()
.background(
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.white)
.shadow(color: Color.black.opacity(0.2), radius: 10.0, x: 0.0, y: 0.0)
)
.frame(height: 350)
.padding(25)
Text(strDateSelected)
.font(.title)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CustomCalendar: UIViewRepresentable {
typealias UIViewType = FSCalendar
@Binding var dateSelected: Date
@Binding var mnthNm: Date
@Binding var pageCurrent: Date
var calendar = FSCalendar()
var today: Date{
return Date()
}
func makeUIView(context: Context) -> FSCalendar {
calendar.dataSource = context.coordinator
calendar.delegate = context.coordinator
calendar.appearance.headerMinimumDissolvedAlpha = 0
return calendar
}
func updateUIView(_ uiView: FSCalendar, context: Context) {
uiView.setCurrentPage(pageCurrent, animated: true) // --->> update calendar view when click on left or right button
}
func makeCoordinator() -> CustomCalendar.Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {
var parent: CustomCalendar
init(_ parent: CustomCalendar) {
self.parent = parent
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
parent.dateSelected = date
}
func calendarCurrentPageDidChange(_ calendar: FSCalendar) {
parent.pageCurrent = calendar.currentPage
parent.mnthNm = calendar.currentPage
}
}
}
Output: