GoogleCalendar like infinite scrollable Calendar for SwiftUI.

Twitter Swift Version SPM compatible License

InfiniteCalendar is infinite scrollable Calendar for iOS written in Swift.

UI/UX design inspired by GoogleCalendar. Implementation inspired by JZCalendarWeekView.


  • Infinite scroll
  • Multiple scroll type
  • Custamazable UI
  • Support long tap gesture actions
  • Support autoScroll for drag
  • Support handling multiple actions
  • Support vibrate feedback like GoogleCalendar


  • Swift 5.1
  • iOS 13.0 or later


Swift Package Manager

InfiniteCalendar is available through Swift Package Manager.

Add it to an existing Xcode project as a package dependency:

  1. From the File menu, select Add Packages…
  2. Enter “” into the package repository URL text field


1. Initialization

You need to define View, ViewModel and CollectionViewCell for display cell on Calendar:

  1. Create View complianted for CellableView protocol:

struct EventCellView: CellableView {
    typealias VM = ViewModel

    // MARK: ViewModel
    struct ViewModel: ICEventable { ... }

    // MARK: View
    var viewModel: ViewModel
    init(_ viewModel: ViewModel) {
        self.viewModel = viewModel
    var body: some View {
  1. Implement ViewModel complianted for ICEventable protocol:

struct ViewModel: ICEventable { 
    private(set) var id: String = UUID().uuidString
    var text: String

    var startDate: Date
    var endDate: Date?
    var intraStartDate: Date
    var intraEndDate: Date
    var editState: EditState?
    var isAllDay: Bool
    init(text: String, start: Date, end: Date?, isAllDay: Bool = false, editState: EditState? = nil) {
    // ! Make sure copy current object, otherwise view won't display properly when SwiftUI View is updated.
    func copy() -> EventCellView.ViewModel {
        var copy = ViewModel(text: text, start: startDate, end: endDate, isAllDay: isAllDay, editState: editState)
        return copy
    static func create(from eventable: EventCellView.ViewModel?, state: EditState?) -> EventCellView.ViewModel {
        if var model = eventable?.copy() {
            model.editState = state
            return model
        return ViewModel(text: "New", start: Date(), end: nil, editState: state)
  1. Create CollectionViewCell complianted for ViewHostingCell protocol:

final class EventCell: ViewHostingCell<EventCellView> {
    override init(frame: CGRect) {
        super.init(frame: frame)
  1. Use InfiniteCalendar with CustomViews:

struct ContentView: View {
    @State var events: [EventCellView.VM] = []
    @State var didTapToday: Bool = false
    @ObservedObject var settings: ICViewSettings = ICViewSettings(
        numOfDays: 3, 
        initDate: Date(), 
        scrollType: .sectionScroll, 
        moveTimeMinInterval: 15, 
        timeRange: (1, 23), 
        withVibration: true

    var body: some View {
        InfiniteCalendar<EventCellView, EventCell, ICViewSettings>(events: $events, settings: settings, didTapToday: $didTapToday)

2. Handling


This method will be called when changed currrent date displayed on calendar. The date can be get is the leftest date on current display.

ex.) Display 3 column dates, 4/1 | 4/2 | 4/3 -> 4/1 can be obtained.

InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onCurrentDateChanged { date in
    currentDate = date


This method will be called when item was tapped.

InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onItemSelected { item in
    selectedItem = item


This method will be called when item was created by long tap with drag gesture.

InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventAdded { item in


This method will be called when item was created by long tap gesture on exist item.

InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventMoved { item in
    if let index = events.firstIndex(where: {$ ==}) {
        events[index] = item


This method will be called when canceled gesture event by some accident or issues.

InfiniteCalendar<EventCellView, EventCell, Settings>(events: $events, settings: settings, didTapToday: $didTapToday)
.onEventCanceled { item in
    print("Canceled some event gesture for \(")

3. Settings

If you want to customize Settings, create SubClass of ICViewSettings.


Number of dispaly dates on a screen

Sample: numOfDays = 1, numOfDays = 3, numOfDays = 7


The display date for lounch app


There is two kinds of scroll type, Section and Page.

SectionType will deside scroll amount by scroll velocity. On the other hand PageType is always scroll to next / prev page with scroll gesture.


Interval minutes time for drag gesture. Default value is 15. Which means when item was created/moved time will move every 15 minutes


The display time on time header as a label. Default value is (1, 23). Which means display 1:00 ~ 23:00.


If vibration is needed during dragging gesture. Default value is true. Vibration feedback is almost same as GoogleCalendar.

4. Custom UI components

You can customize each components on the bellow.

  • SupplementaryCell (Use as SupplementaryCell in CollectionView)
    • TimeHeader
    • DateHeader
    • DateHeaderCorner
    • AllDayHeader
    • AllDayHeaderCorner
    • Timeline
  • DecorationCell (Use as DecorationCell in CollectionView)
    • TimeHeaderBackground
    • DateHeaderBackground
    • AllDayHeaderBackground

Default components

Component calss is define as typealias to customize.

public typealias TimeHeader = ICTHeader
public typealias TimeHeaderBackground = ICTHeaderBackground
public typealias DateHeader = ICDHeader
public typealias DateHeaderBackground = ICDHeaderBackground
public typealias DateHeaderCorner = ICDCorner
public typealias AllDayHeader = ICAllDayHeader
public typealias AllDayHeaderBackground = ICAllDayHeaderBackground
public typealias AllDayHeaderCorner = ICAllDayCorner
public typealias Timeline = ICTimeline

Sample custom component (Timeline)

// Timeline is SupplementaryCell type. So must SubClass of `ViewHostingSupplementaryCell`
public final class CustomTimeline: ViewHostingSupplementaryCell<CustomTimelineView> {}

public struct CustomTimelineView: ICComponentView {
    // Must use ICTimelineItem
    public typealias Item = ICTimelineItem 

    var item: Item
    public init(_ item: Item) {
        self.item = item
    public var body: some View {
            .frame(height: 1.0)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .opacity(item.isDisplayed ? 1 : 0)

Create SubClass of ICViewSettings to set custom component.

class Settings: ICViewSettings {
    typealias Timeline = CustomTimeline

    init(numOfDays: Int, initDate: Date) {
        self.numOfDays = numOfDays
        self.initDate = initDate



InfiniteCalendar is available under the MIT license. See the LICENSE file for more info.


View Github