Nico Cobelo
Nico Cobelo

Reputation: 767

Adding Next/ Prev buttons to FSCalendar in SwiftUI

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:

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?

Upvotes: 1

Views: 1556

Answers (1)

Kishan Bhatiya
Kishan Bhatiya

Reputation: 2368

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:

enter image description here

Upvotes: 3

Related Questions