2

I need to use custom calendar instead of SwiftUI DatePicker. For this purpose I added pod 'KDCalendar', '~> 1.8.9' in the Podfile after that MyCalendar structure created as following:

import SwiftUI
import KDCalendar

public struct MyCalendar: UIViewRepresentable {

    public func makeUIView(context: Context) -> CalendarView {
        return CalendarView()
    }

    public func updateUIView(_ calendar: CalendarView, context: Context) {
        let date: Date = Date()
        calendar.selectDate(date)
    }
}

When I'm going to use it in any stack it's shows only the week name and other parts of the calendar omitted from view and following errors occurred:

enter image description here

[Assert] negative or zero item sizes are not supported in the flow layout

Your cooperation highly expected to resolve this issue.

4
  • 1
    Stack do not work as expected in case of scroll view or table view. As I am seeing, KDCalendar include table view. So you can try to keep calendar view outside the stack view. Commented Jan 21, 2020 at 5:39
  • When I added it's to sheet, also same things happened! Commented Jan 21, 2020 at 6:50
  • 1
    Try github.com/WenchaoD/FSCalendar Commented Jan 21, 2020 at 10:45
  • Customisable as you want -> github.com/yodagamaheshan/DateGrid Commented Oct 28, 2020 at 6:00

2 Answers 2

4

You can try FSCalendar too. It's little bit complex where there is lots of options for configurability. Please find the simplest implementation of FSCalendar as following:

Usage


import UIKit
import SwiftUI
import FSCalendar

MyCalendar().frame(minWidth: 200, minHeight: 320).padding(.leading, 5)

MyCalendar


struct MyCalendar: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<MyCalendar>) -> MyCalendarController {
        let calendar: MyCalendarController = .init()
        return calendar
    }

    func updateUIViewController(_ calendar: MyCalendarController, context: UIViewControllerRepresentableContext<MyCalendar>) {
        // MARK: - TODO
    }
}     

MyCalendarController


class MyCalendarController: UIViewController, FSCalendarDelegateAppearance {
    let secondary: UIColor = .parse(0xE0B355)
    let primary  : UIColor = .parse(0x346C7C)
    let tersiary : UIColor = .parse(0xE7EEEF)
    
    fileprivate let formatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    }()
    
    fileprivate weak var calendar: FSCalendar!
    
    override func loadView() {
        let width: CGFloat = UIScreen.main.bounds.width - 40
        let frame: CGRect  = .init(x: 0, y: 0, width: width, height: 300)
        let view:  UIView  = .init(frame: frame)
        self.view = view
        
        let calendar: FSCalendar = .init(frame: frame)
        calendar.allowsMultipleSelection = false
        calendar.dataSource = self
        calendar.delegate = self
        
        view.addSubview(calendar)
        self.calendar = calendar
        
        calendar.calendarHeaderView.backgroundColor = self.primary
        calendar.calendarWeekdayView.backgroundColor = self.primary
        calendar.appearance.headerTitleColor = self.tersiary
        calendar.appearance.weekdayTextColor = self.tersiary
        
        calendar.appearance.eventSelectionColor = self.tersiary
        calendar.appearance.eventDefaultColor = self.primary
        calendar.appearance.eventOffset = CGPoint(x: 0, y: -7)
        
        calendar.appearance.todaySelectionColor = self.primary
        calendar.appearance.selectionColor = self.secondary
        calendar.appearance.todayColor = self.primary
        
        calendar.appearance.titleWeekendColor = self.secondary
        calendar.appearance.titleDefaultColor = self.primary

        calendar.swipeToChooseGesture.isEnabled = true
        let scopeGesture = UIPanGestureRecognizer(target: calendar, action: #selector(calendar.handleScopeGesture(_:)));
        scopeGesture.delegate = self
        calendar.addGestureRecognizer(scopeGesture)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.calendar.scope = .month
        self.calendar.select(Date.init())
        self.calendar.accessibilityIdentifier = "calendar"
    }
}

Extensions


extension MyCalendarController: FSCalendarDataSource {
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
        debugPrint("did select date \(self.formatter.string(from: date))")
        let selectedDates = calendar.selectedDates.map({self.formatter.string(from: $0)})
        debugPrint("selected dates is \(selectedDates)")
        if monthPosition == .next || monthPosition == .previous {
            calendar.setCurrentPage(date, animated: true)
        }
    }
}

extension MyCalendarController: FSCalendarDelegate {
    func calendar(_ calendar: FSCalendar, boundingRectWillChange bounds: CGRect, animated: Bool) {
        self.calendar.frame.size.height = bounds.height
        self.view.layoutIfNeeded()
    }
}

extension MyCalendarController: UIGestureRecognizerDelegate {
    func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        debugPrint("UIGestureRecognizer")
        return true
    }
}

extension UIColor {
    static func parse(_ hex: UInt32, alpha: Double = 1.0) -> UIColor {
        let red   = CGFloat((hex & 0xFF0000) >> 16)/256.0
        let green = CGFloat((hex & 0xFF00) >> 8)/256.0
        let blue  = CGFloat(hex & 0xFF)/256.0
        return UIColor(red: red, green: green, blue: blue, alpha: CGFloat(alpha))
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I appreciate you doing this! All looks good - I have this implementation rendered in my project now and will do some tweaking and try to learn the methods for configuration.. One last question: What is ThemeSettings.width in your code? My guess is it is something specific to your project because I found no other references to it in FSCalendar or Swift. I have it "working" with a random Float I picked out of my head just to make it build. If you could share what that does that would be appreciated as I assume it is doing something more clever with getting the width of the device?
You have to use UIScreen.main.bounds.width instead of ThemeSettings.width or any calculated value as your demand. It's used for initialisation nothing else.
2

To view KDCalendar, simply need to add dataSource and delegate as below. This will resolve the KDCalendar rendering issue.


import SwiftUI
import KDCalendar

public struct MyCalendar: UIViewRepresentable {
    var date: Date = .init()

    public func makeUIView(context: Context) -> CalendarView {
        let calendar: CalendarView = .init()
        calendar.setDisplayDate(self.date, animated: false)
        calendar.selectDate(self.date)
        calendar.dataSource = self
        calendar.delegate = self
        return calendar;
    }

    public func updateUIView(_ calendar: CalendarView, context: Context) {
        calendar.selectDate(self.date)
    }
}

extension MyCalendar: CalendarViewDataSource {
    public func startDate() -> Date {
        return Date.init()
    }

    public func endDate() -> Date {
        var month:DateComponents = .init()
        month.month = 2
        return Calendar.current.date(byAdding: month, to: Date.init())!
    }

    public func headerString(_ date: Date) -> String? {
        return nil
    }
}


extension MyCalendar: CalendarViewDelegate {
    public func calendar(_ calendar: CalendarView, didDeselectDate date: Date) {}
    public func calendar(_ calendar: CalendarView, didScrollToMonth date: Date) {}
    public func calendar(_ calendar: CalendarView, didSelectDate date: Date, withEvents events: [CalendarEvent]) {}
    public func calendar(_ calendar: CalendarView, didLongPressDate date: Date, withEvents events: [CalendarEvent]?) {}

    public func calendar(_ calendar: CalendarView, canSelectDate date: Date) -> Bool {
        return true
    }
}

5 Comments

Using this method, did the event manager functions work properly? I have used your method to make CalendarView UIView, UIViewRepresentable. The calendar has rendered properly but the date selector and event manager does not work. Did you come across this?
Hi, Gene. Actually there were some issues to add event calendar.addEvent(title: String, date: Date) or calendar.addEvent(title: String, date: Date, duration: NSInteger) . Also there was a warning like [Assert] negative or zero item sizes are not supported in the flow layout. Why we decided to use FSCalendar instead of it. FSCalendar little bit complex but lots of feasibility and configurability. UIViewControllerRepresentable will help you to implement FSCalendar.
could you share your implementation of FSCalendar in SwiftUI for the community?
Hi, Gene. Please find the second answer, hope it will solve your problem, also help the community.
This can be customisable as you want -> github.com/yodagamaheshan/DateGrid

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.