A declarative, performant, iOS calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.

Overview

HorizonCalendar

A declarative, performant, calendar UI component that supports use cases ranging from simple date pickers all the way up to fully-featured calendar apps.

Swift Package Manager compatible Carthage compatible Version License Platform Swift

Introduction

HorizonCalendar is UIKit library for displaying a range of dates in a vertically-scrolling or horizontally-scrolling calendar component. Its declarative API makes updating the calendar straightforward, while also providing many customization points to support a diverse set of designs and use cases.

Features:

  • Supports all calendars from Foundation.Calendar (Gregorian, Japanese, Hebrew, etc.)
  • Display months in a vertically-scrolling or horizontally-scrolling layout
  • Declarative API that enables unidirectional data flow for updating the content of the calendar
  • A custom layout system that enables virtually infinite date ranges without increasing memory usage
  • Pagination for horizontally-scrolling calendars
  • Specify custom views for individual days, month headers, and days of the week
  • Specify custom views to highlight date ranges
  • Specify custom views to overlay parts of the calendar, enabling features like tooltips
  • A day selection handler to monitor when a day is tapped
  • Customizable layout metrics
  • Pin the days-of-the-week row to the top
  • Show partial boundary months (exactly 2020-03-14 to 2020-04-20, for example)
  • Scroll to arbitrary dates and months, with or without animation
  • Robust accessibility support
  • Inset the content without affecting the scrollable region using UIView layout margins
  • Separator below the days-of-the-week row
  • Right-to-left layout support

HorizonCalendar serves as the foundation for the date pickers and calendars used in Airbnb's highest trafficked flows.

Search Stays Availability Calendar Wish List Experience Reservation Experience Host Calendar Management
Search Stay Availability Calendar Wish List Experience Reservation Experience Host Calendar Management

Table of Contents

Example App

An example app is available to showcase and enable you to test some of HorizonCalendar's features. It can be found in ./Example/HorizonCalendarExample.xcworkspace.

Note: Make sure to use the .xcworkspace file, and not the .xcodeproj file, as the latter does not have access to HorizonCalendar.framework.

Demos

The example app has several demo view controllers to try, with both vertical and horizontal layout variations:

Demo Picker

Single Day Selection

Vertical Horizontal
Single Day Selection Vertical Single Day Selection Horizontal

Day Range Selection

Vertical Horizontal
Day Range Selection Vertical Day Range Selection Horizontal

Selected Day Tooltip

Vertical Horizontal
Selected Day Tooltip Vertical Selected Day Tooltip Horizontal

Scroll to Day with Animation

Vertical Horizontal
Scroll to Day with Animation Vertical Scroll to Day with Animation Horizontal

Integration Tutorial

Requirements

  • Deployment target iOS 11.0+
  • Swift 5+
  • Xcode 10.2+

Installation

Swift Package Manager

To install HorizonCalendar using Swift Package Manager, add .package(name: "HorizonCalendar", url: "https://github.com/airbnb/HorizonCalendar.git", from: "1.0.0")," to your Package.swift, then follow the integration tutorial here.

Carthage

To install HorizonCalendar using Carthage, add github "airbnb/HorizonCalendar" to your Cartfile, then follow the integration tutorial here.

CocoaPods

To install HorizonCalendar using CocoaPods, add pod 'HorizonCalendar' to your Podfile, then follow the integration tutorial here.

Building a CalendarView

Once you've installed HorizonCalendar into your project, getting a basic calendar working is just a few steps.

Basic Setup

Importing HorizonCalendar

At the top of the file where you'd like to use HorizonCalendar (likely a UIView or UIViewController subclass), import HorizonCalendar:

import HorizonCalendar 

Initializing a CalendarView with CalendarViewContent

CalendarView is the UIView subclass that renders the calendar. All visual aspects of CalendarView are controlled through a single type - CalendarViewContent. To create a basic CalendarView, you initialize one with an initial CalendarViewContent:

let calendarView = CalendarView(initialContent: makeContent())
private func makeContent() -> CalendarViewContent {
  let calendar = Calendar.current

  let startDate = calendar.date(from: DateComponents(year: 2020, month: 01, day: 01))!
  let endDate = calendar.date(from: DateComponents(year: 2021, month: 12, day: 31))!

  return CalendarViewContent(
    calendar: calendar,
    visibleDateRange: startDate...endDate,
    monthsLayout: .vertical(options: VerticalMonthsLayoutOptions()))
}

At a minimum, CalendarViewContent must be initialized with a Calendar, a visible date range, and a months layout (either vertical or horizontal). The visible date range will be interpreted as a range of days using the Calendar instance passed in for the calendar parameter.

For this example, we're using a Gregorian calendar, a date range of 2020-01-01 to 2021-12-31, and a vertical months layout.

Make sure to add calendarView as a subview, then give it a valid frame either using Auto Layout or by manually setting its frame property. If you're using Auto Layout, note that CalendarView does not have an intrinsic content size.

view.addSubview(calendarView)

calendarView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([
  calendarView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
  calendarView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
  calendarView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
  calendarView.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
])

At this point, building and running your app should result in something that looks like this:

Basic Calendar

Customizing CalendarView

Providing a custom view for each day

HorizonCalendar comes with default views for month headers, day of week items, and day items. You can also provide custom views for each of these item types, enabling you to display whatever custom content makes sense for your app.

Since all visual aspects of CalendarView are configured through CalendarViewContent, we'll expand on our makeContent function. Let's start by providing a custom view for each day in the calendar:

private func makeContent() -> CalendarViewContent {
  return CalendarViewContent(
    calendar: calendar,
    visibleDateRange: today...endDate,
    monthsLayout: .vertical(VerticalMonthsLayoutOptions()))
    
    .withDayItemModelProvider { day in
      // Return a `CalendarItemModel` representing the view for each day
    }
}

The withDayItemModelProvider(_:) function on CalendarViewContent returns a new CalendarViewContent instance with the custom day item model provider configured. This function takes a single parameter - a provider closure that returns a CalendarItemModel for a given Day.

CalendarItemModel is a type that abstracts away the creation and configuration of a UIView. It's generic over a ViewRepresentable type, which can be any type conforming to CalendarItemViewRepresentable. You can think of CalendarItemViewRepresentable as a blueprint for creating and updating instances of a particular type of view to be displayed in the calendar. For example, if we want to use a UILabel for our custom day view, we'll need to create a type that knows how to create and update that label. Here's a simple example:

import HorizonCalendar

struct DayLabel: CalendarItemViewRepresentable {

  /// Properties that are set once when we initialize the view.
  struct InvariantViewProperties: Hashable {
    let font: UIFont
    let textColor: UIColor
    let backgroundColor: UIColor
  }

  /// Properties that will vary depending on the particular date being displayed.
  struct ViewModel: Equatable {
    let day: Day
  }

  static func makeView(
    withInvariantViewProperties invariantViewProperties: InvariantViewProperties)
    -> UILabel
  {
    let label = UILabel()

    label.backgroundColor = invariantViewProperties.backgroundColor
    label.font = invariantViewProperties.font
    label.textColor = invariantViewProperties.textColor

    label.textAlignment = .center
    label.clipsToBounds = true
    label.layer.cornerRadius = 12
    
    return label
  }

  static func setViewModel(_ viewModel: ViewModel, on view: UILabel) {
    view.text = "\(viewModel.day)"
  }

}

CalendarItemViewRepresentable requires us to implement a static makeView function, which should create and return a view given a set of invariant view properties. We want our label to have a configurable font and text color, so we've made those configurable via the InvariantViewProperties type. In our makeView function, we use those invariant view properties to create and configure an instance of our label.

CalendarItemViewRepresentable also requires us to implement a static setViewModel function, which should update all data-dependent properties (like the day text) on the provided view.

Now that we have a type conforming to CalendarItemViewRepresentable, we can use it to create a CalendarItemModel to return from the day item model provider:

  return CalendarViewContent(...)

    .withDayItemModelProvider { day in
      CalendarItemModel<DayLabel>(
        invariantViewProperties: .init(
          font: UIFont.systemFont(ofSize: 18), 
          textColor: .darkGray,
          backgroundColor: .clear)
        viewModel: .init(day: day))
    }

Similar item model provider functions are available to customize the views used for month headers, day-of-the-week items, and more.

If you build and run your app, it should now look like this:

Custom Day Views

Adjusting layout metrics

We can also use CalendarViewContent to adjust layout metrics. We can improve the layout of our current CalendarView by adding some additional spacing between individual days and months:

  return CalendarViewContent(...)
    .withDayItemModelProvider { ... }

    .withInterMonthSpacing(24)
    .withVerticalDayMargin(8)
    .withHorizontalDayMargin(8)

Just like when we configured a custom day view via the day item provider, changes to layout metrics are also done through CalendarViewContent. withInterMonthSpacing(_:), withVerticalDayMargin(_:), and withHorizontalDayMargin(_:) each return a mutated CalendarViewContent with the corresponding layout metric value updated, enabling you to chain function calls together to produce a final content instance.

After building and running your app, you should see a much less cramped layout:

Custom Layout Metrics

Adding a day range indicator

Day range indicators are useful for date pickers that need to highlight not just individual days, but ranges of days. HorizonCalendar offers an API to do exactly this via the CalendarViewContent function withDayRangeItemModelProvider(for:_:). Similar to what we did for our custom day item model provider, for day ranges, we need to provide a CalendarItemModel for each day range we want to highlight.

First, we need to create a ClosedRange<Date> that represents the day range for which we'd like to provide a CalendarItemModel. The Dates in our range will be interpreted as Days using the Calendar instance with which we initialized our CalendarViewContent.

  let lowerDate = calendar.date(from: DateComponents(year: 2020, month: 01, day: 20))!
  let upperDate = calendar.date(from: DateComponents(year: 2020, month: 02, day: 07))!
  let dateRangeToHighlight = lowerDate...upperDate

Next, we need to invoke the withDayRangeItemModelProvider(for:_:) on our CalendarViewContent:

  return CalendarViewContent(...)
    ...
    
    .withDayRangeItemModelProvider(for: [dateRangeToHighlight]) { dayRangeLayoutContext in 
      // Return a `CalendarItemModel` representing the view that highlights the entire day range
    }

For each day range derived from the Set<ClosedRange<Date>> passed into this function, our day range item model provider closure will be invoked with a context instance that contains all of the information needed for us to render a view to be used to highlight a particular day range. Here is an example implementation of such a view:

import UIKit

final class DayRangeIndicatorView: UIView {

  private let indicatorColor: UIColor

  init(indicatorColor: UIColor) {
    self.indicatorColor = indicatorColor
    super.init(frame: frame)
    backgroundColor = .clear
  }

  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

  var framesOfDaysToHighlight = [CGRect]() {
    didSet {
      guard framesOfDaysToHighlight != oldValue else { return }
      setNeedsDisplay()
    }
  }

  override func draw(_ rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()
    context?.setFillColor(indicatorColor.cgColor)

    // Get frames of day rows in the range
    var dayRowFrames = [CGRect]()
    var currentDayRowMinY: CGFloat?
    for dayFrame in framesOfDaysToHighlight {
      if dayFrame.minY != currentDayRowMinY {
        currentDayRowMinY = dayFrame.minY
        dayRowFrames.append(dayFrame)
      } else {
        let lastIndex = dayRowFrames.count - 1
        dayRowFrames[lastIndex] = dayRowFrames[lastIndex].union(dayFrame)
      }
    }

    // Draw rounded rectangles for each day row
    for dayRowFrame in dayRowFrames {
      let roundedRectanglePath = UIBezierPath(roundedRect: dayRowFrame, cornerRadius: 12)
      context?.addPath(roundedRectanglePath.cgPath)
      context?.fillPath()
    }
  }

}

Next, we need a type that conforms to CalendarItemViewRepresentable that knows how to create and update instances of DayRangeIndicatorView. To make things easy, we can just make our view conform to this protocol:

import HorizonCalendar

extension DayRangeIndicatorView: CalendarItemViewRepresentable {

  struct InvariantViewProperties: Hashable {
    let indicatorColor = UIColor.blue.withAlphaComponent(0.15)
  }

  struct ViewModel: Equatable {
    let framesOfDaysToHighlight: [CGRect]
  }

  static func makeView(
    withInvariantViewProperties invariantViewProperties: InvariantViewProperties)
    -> DayRangeIndicatorView
  {
    DayRangeIndicatorView(indicatorColor: invariantViewProperties.indicatorColor)
  }

  static func setViewModel(_ viewModel: ViewModel, on view: DayRangeIndicatorView) {
    view.framesOfDaysToHighlight = viewModel.framesOfDaysToHighlight
  }

}

Last, we need to return a CalendarItemModel representing our DayRangeIndicatorView from the day range item model provider closure:

  return CalendarViewContent(...)
    ...
    
    .withDayRangeItemModelProvider(for: [dateRangeToHighlight]) { dayRangeLayoutContext in
      CalendarItemModel<DayRangeIndicatorView>(
        invariantViewProperties: .init(indicatorColor: UIColor.blue.withAlphaComponent(0.15)),
        viewModel: .init(framesOfDaysToHighlight: dayRangeLayoutContext.daysAndFrames.map { $0.frame }))
    }

If you build and run the app, you should see a day range indicator view that highlights 2020-01-20 to 2020-02-07:

Day Range Indicator

Adding a tooltip

HorizonCalendar provides an API to overlay parts of the calendar with custom views. One use case that this enables is adding tooltips to certain days - a feature that's used in the Airbnb app to inform users when their checkout date must be a certain number of days in the future from their check-in date.

First, we need to decide on the locations of the items that we'd like to overlay with our own custom view. We can overlay a day or a monthHeader - the two cases available on CalendarViewContent.OverlaidItemLocation. Let's overlay the day at 2020-01-15:

  let dateToOverlay = calendar.date(from: DateComponents(year: 2020, month: 01, day: 15))!
  let overlaidItemLocation: CalendarViewContent.OverlaidItemLocation = .day(containingDate: dateToOverlay) 

Like all other customizations, we'll add an overlay by calling a function on our CalendarViewContent instance that configures an overlay item model provider closure:

  return CalendarViewContent(...)
    ...
    
    .withOverlayItemModelProvider(for: [overlaidItemLocation]) { overlayLayoutContext in
      // Return a `CalendarItemModel` representing the view to use as an overlay for the overlaid item location
    }

For each overlaid item location in the Set<CalendarViewContent.OverlaidItemLocation> passed into this function, our overlay item model provider closure will be invoked with a context instance that contains all of the information needed for us to render a view to be used as an overlay for a particular overlaid item location. Here is an example implementation of a tooltip overlay view:

import UIKit

final class TooltipView: UIView {

  init(backgroundColor: UIColor, borderColor: UIColor, font: UIFont, textColor: UIColor) {
    super.init(frame: .zero)

    backgroundView.backgroundColor = backgroundColor
    backgroundView.layer.borderColor = borderColor
    addSubview(backgroundView)

    label.font = font
    label.textColor = textColor
    addSubview(label)
  }

  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
  
  var text: String {
    get { label.text ?? "" }
    set { label.text = newValue }
  }

  var frameOfTooltippedItem: CGRect? {
    didSet {
      guard frameOfTooltippedItem != oldValue else { return }
      setNeedsLayout()
    }
  }

  override func layoutSubviews() {
    super.layoutSubviews()

    guard let frameOfTooltippedItem = frameOfTooltippedItem else { return }

    label.sizeToFit()
    let labelSize = CGSize(
      width: min(label.bounds.size.width, bounds.width),
      height: label.bounds.size.height)

    let backgroundSize = CGSize(width: labelSize.width + 16, height: labelSize.height + 16)

    let proposedFrame = CGRect(
      x: frameOfTooltippedItem.midX - (backgroundSize.width / 2),
      y: frameOfTooltippedItem.minY - backgroundSize.height - 4,
      width: backgroundSize.width,
      height: backgroundSize.height)

    let frame: CGRect
    if proposedFrame.maxX > bounds.width {
      frame = proposedFrame.applying(.init(translationX: bounds.width - proposedFrame.maxX, y: 0))
    } else if proposedFrame.minX < 0 {
      frame = proposedFrame.applying(.init(translationX: -proposedFrame.minX, y: 0))
    } else {
      frame = proposedFrame
    }

    backgroundView.frame = frame
    label.center = backgroundView.center
  }

  // MARK: Private

  private lazy var backgroundView: UIView = {
    let view = UIView()
    view.layer.borderWidth = 1
    view.layer.cornerRadius = 6
    view.layer.shadowColor = UIColor.black.cgColor
    view.layer.shadowOpacity = 0.8
    view.layer.shadowOffset = .zero
    view.layer.shadowRadius = 8
    return view
  }()

  private lazy var label: UILabel = {
    let label = UILabel()
    label.textAlignment = .center
    label.lineBreakMode = .byTruncatingTail
    return label
  }()

}

Next, we need a type that conforms to CalendarItemViewRepresentable that knows how to create and update instances of TooltipView. To make things easy, we can just make our view conform to this protocol:

import HorizonCalendar

extension TooltipView: CalendarItemViewRepresentable {

  struct InvariantViewProperties: Hashable {
    let backgroundColor: UIColor
    let borderColor: UIColor
    let font: UIFont
    let textColor: UIColor
  }

  struct ViewModel: Equatable {
    let frameOfTooltippedItem: CGRect?
    let text: String
  }

  static func makeView(
    withInvariantViewProperties invariantViewProperties: InvariantViewProperties)
    -> TooltipView
  {
  TooltipView(
    borderColor: invariantViewProperties.borderColor, 
    font: invariantViewProperties.font, 
    textColor: invariantViewProperties.textColor)
  }

  static func setViewModel(_ viewModel: ViewModel, on view: TooltipView) {
    view.frameOfTooltippedItem = viewModel.frameOfTooltippedItem
    view.text = viewModel.text
  }

}

Last, we need to return a CalendarItemModel representing our TooltipView from the overlay item model provider closure:

  return CalendarViewContent(...)
    ...
    
    .withOverlayItemModelProvider(for: [overlaidItemLocation]) { overlayLayoutContext in
      CalendarItemModel<TooltipView>(
        invariantViewProperties: .init(
          backgroundColor: .white, 
          borderColor: .black, 
          font: UIFont.systemFont(ofSize: 16), 
          textColor: .black),
        viewModel: .init(
          frameOfTooltippedItem: overlayLayoutContext.overlaidItemFrame, 
          text: "Dr. Martin Luther King Jr.'s Birthday"))
    }

If you build and run the app, you should see a tooltip view hovering above 2020-01-15:

Tooltip View

Responding to day selection

If you're building a date picker, you'll most likely need to respond to the user tapping on days in the calendar. To do this, provide a day selection handler closure via CalendarView's daySelectionHandler:

calendarView.daySelectionHandler = { [weak self] day in
  self?.selectedDay = day
}
private var selectedDay: Day?

The day selection handler closure is invoked whenever a day in the calendar is selected. You're provided with a Day instance for the day that was selected. If we want to highlight the selected day once its been tapped, we'll need to create a new CalendarViewContent with a day calendar item model that looks different for the selected day:

  let selectedDay = self.selectedDay

  return CalendarViewContent(...)

    .withDayItemModelProvider { day in
      var invariantViewProperties = DayLabel.InvariantViewProperties(
        font: UIFont.systemFont(ofSize: 18), 
        textColor: .darkGray,
        backgroundColor: .clear)

      if day == selectedDay {
        invariantViewProperties.textColor = .white
        invariantViewProperties.backgroundColor = .blue
      }
      
      return CalendarItemModel<DayLabel>(
        invariantViewProperties: invariantViewProperties,
        viewModel: .init(day: day))
  }

Last, we'll change our day selection handler so that it not only stores the selected day, but also sets an updated content instance on calendarView:

calendarView.daySelectionHandler = { [weak self] day in
  guard let self = self else { return }
  
  self.selectedDay = day
  
  let newContent = self.makeContent()
  self.calendarView.setContent(newContent)
}

After building and running the app, tapping days should cause them to turn blue:

Day Selection

Technical Details

If you'd like to learn about how HorizonCalendar was implemented, check out the Technical Details document. It provides an overview of HorizonCalendar's architecture, along with information about why it's not implemented using UICollectionView.

Contributions

HorizonCalendar welcomes fixes, improvements, and feature additions. If you'd like to contribute, open a pull request with a detailed description of your changes.

As a rule of thumb, if you're proposing an API-breaking change or a change to existing functionality, consider proposing it by opening an issue, rather than a pull request; we'll use the issue as a public forum for discussing whether the proposal makes sense or not. See CONTRIBUTING for more details.

Authors

Bryan Keller

Maintainers

Bryan Keller

Bryn Bodayle

If you or your company has found HorizonCalendar to be useful, let us know!

License

HorizonCalendar is released under the Apache License 2.0. See LICENSE for details.

Comments
  • [feat] Pagination for Horizontal Calendars

    [feat] Pagination for Horizontal Calendars

    Hey Contributors, just wanted to say thanks!

    I have actually already switched over to using HorizonCalendar in one of our applications that required a vertical calendar implementation and I must say, it is one of the easiest declarative calendar options available,. so much so that we're starting to move over another application over to it.

    However, we require a horizontal calendar, with pagination. I went and attempted to set isPagingEnabled to the underlying UIScrollView, but that definitely lead to some bizarre behavior (specifically around the width I believe), and the pagination isn't true.

    Is there any hint at adding an official way to set pagination for horizontal month layouts?

    enhancement 
    opened by saudeon 14
  • feat: provide ability to add footer to month

    feat: provide ability to add footer to month

    Details

    • Adds in the ability to provide a footer for a month.
    • Provides a default footer with an empty view.

    Related Issue

    #31

    Motivation and Context

    A request for being able to provide views for month footers was made.

    How Has This Been Tested

    • Made sure that original tests compile and run.

    Types of changes

    • [ ] Docs change / refactoring / dependency upgrade
    • [ ] Bug fix (non-breaking change which fixes an issue)
    • [x] New feature (non-breaking change which adds functionality)
    • [ ] Breaking change (fix or feature that would cause existing functionality to change)

    Checklist

    • [x] My code follows the code style of this project.
    • [ ] My change requires a change to the documentation.
    • [ ] I have updated the documentation accordingly.
    • [x] I have read the CONTRIBUTING document.
    • [ ] I have added tests to cover my changes.
    enhancement 
    opened by saudeon 12
  • Question: Center Horizontal Calendar

    Question: Center Horizontal Calendar

    Hey Guys,

    Don't know if this belongs here, but I am having troubling finding a way to center the horizontal calendar, as opposed to aligning it to the left, as the default implementation suggests. In the same vein, I am also looking for the correct way to center the Month Title (August 2020) in the center of the screen, rather than aligning it to the left as well. Basically just trying to show only one month at a time with equal padding to the trailing and leading edge of the calendar, as well as center the Month Title. My apologies if these are already features and I am just missing them. I have attached two screenshots to demonstrate what I mean in regards to the alignment issue, with one being what I currently have and one being my desired outcome.

    Edit: I did end up figuring out the centered header by working off the default implementation, but needed to basically copy to my source code the extended function "firstDate" since it was internally protected. Is there an easier way around this?

    Thanks

    Screen Shot 2020-08-08 at 5 04 35 PM

    Screen Shot 2020-08-08 at 5 04 22 PM

    opened by zachterrell57 11
  • Calendar not being refreshed when new events are fetched from the sever

    Calendar not being refreshed when new events are fetched from the sever

    First of all, thanks for this amazing library.

    I've noticed the following issue when using the library, the data is not being refreshed properly after fetching data from the server. I am attaching screenshots at the bottom.

    example:

      var events: [String: Any] = [:]
    
      var presenter: CalendarPresenterDelegate!
    
      override func viewDidLoad() {
          super.viewDidLoad()
          /// some networking logic
          presenter.fetchEvents()
      }
    
      override func makeContext() -> CalendarViewContent {
    
            let startDate = Date()
            let endDate = calendar.date(from: DateComponents(year: 2021, month: 12, day: 31))!
    
            return CalendarViewContent(calendar: calendar,
                                       visibleDateRange: startDate...endDate,
                                       monthsLayout: .horizontal(monthWidth: min(view.frame.height, view.frame.width) - 40))
                .withDayItemProvider { day -> CalendarItem<EventLabel, Day> in
                    let date = self.calendar.date(from: day.components)!
                    let isSelectedDay = self.selectedDates.contains(date)
                    return CalendarItem<EventLabel, Day>(
                        viewModel: day,
                        styleID: isSelectedDay ? "SelectedDayLabelStyle" : "DayLabelStyle",
                        buildView: {
                            return EventLabel()
                        },
                        updateViewModel: self.setupEvent)
                }
                .withInterMonthSpacing(16)
                .withVerticalDayMargin(8)
                .withHorizontalDayMargin(8)
        }
    
        func setEvents(events: [String: Any]) {
            self.events = events
            let newContent = makeContent()
            calendarView.setContent(newContent)
        }
    
        override func setupEvent(label: EventLabel, day: Day) {
    
            label.text = "\(day.day)"
            // clear the events
            label.hasNoEvents()
    
            if isSelectedDay {
                label.backgroundColor = Colors.colorAccent
            } else {
                label.backgroundColor = isToday ? Colors.colorPrimary : .clear
            }
    
            guard let object = getEvents(date: date) else {
                return
            }
    
            if (object["event1"] as? Bool) == true {
                label.hasEvent1()
                label.textColor = .lightGray
                return
            }
    
            if (object["event2"] as? [String])?.isEmpty == false {
                label.hasEvent2()
            }
        }
    

    This is what we get after setEvents being called, however the result we need is in the second image: image 1 Screen Shot 2020-07-14 at 7 11 14 PM image 2 Screen Shot 2020-07-14 at 7 11 00 PM

    PS: if we scroll to September and back the UI refreshes and the UI updates properly. it seems as if the calendarView.setContent(newContent) is not setting the content properly.

    opened by mustiikhalil 11
  • Check In and Check Out custom time for highlighting days

    Check In and Check Out custom time for highlighting days

    Hi Airbnb Team,

    This is my second time asking question.

    1. Is it possible specify time ie. hours for check in and out highlighting as in screenshot.
    Screen Shot 2021-01-29 at 10 58 04 AM
    1. Can I make bigger in size for check in guest images? Similar in screenshot! Overall is it even achievable as design in airbnb calendar?

    Thank you for your time!

    question 
    opened by bekzodrakhmatof 9
  • Make sure it also works with tilted screens

    Make sure it also works with tilted screens

    If one turns on the other screen orientations for the device and run the example, this calendar is not rendering properly anymore. Make sure that those device orientations are supported.

    bug 
    opened by ruurd 8
  • Want to add tooltip in day range selection

    Want to add tooltip in day range selection

    Hello, Thanks for making this beautiful library and open source.

    I want to add multiple tooltip in day range selection. Suppose, I want to select a range from 15 Nov 2022 to 30 Nov 2022, then it should show tooltip on 15 Nov 2022 as "Start Date" and 30th Nov 2022 as "End Date".

    Please provide your comments because I need to implement that functionality.

    opened by Satish24sp 6
  • Horizontal Calendar Leading and Trailing sizes are different

    Horizontal Calendar Leading and Trailing sizes are different

    Hi Airbnb Team,

    Thank you for applying pagination to the calendar.

    For some reason my calendar leading and trailing spaces are different. I want to use full screen width for horizontal calendar.

    See the attached image.

    IMG_0649

    Thank you for the answer!

    opened by bekzodrakhmatof 6
  • Multiple dates in a month are blanked out.

    Multiple dates in a month are blanked out.

    While leaving default code, certain dates in each month are blank. When selected they are show but otherwise they are blank... this issue does not occur to other unselected dates.

    opened by isaacthedeveloper 6
  • Ability to configure scrolling behaviour

    Ability to configure scrolling behaviour

    Hey,

    first of all, thank you for open-sourcing this neat library!

    Secondly, I wanted to ask you: would you consider adding some kind of ability to configure scrolling behaviour (perhaps to MonthsLayout enum via LayoutOptions)? Because I wanted to implement horizontal calendar with snapping like behaviour (where you have single month shown on the screen and then using swipe gesture or buttons move to next/previous month) and the only way how I managed to achieve that was by going into view hierarchy and disabling scroll on NoContentInsetAdjustmentScrollView which feels kinda hacky as it would stop working if at some point you decide to move scrollview somewhere else.

    Also, I was curious: does HorizonCalendar support RTL layout? Because in order to achieve proper scrolling on RTL I had to change transform of CalendarView

    Thanks

    enhancement 
    opened by Pattio 6
  • How to set the size of day items?

    How to set the size of day items?

    I'm having a hard time understanding how to set the size of items to get a more compact month.

    When the horizontal/vertical day margins are 0, they end up being huge - like 80pt squares. When I set the margins to be 32, then they're appropriately sized, but they're still taking up the same space (because of the margins)

    How do I configure the calendar so that the items are smaller, like 32 with 0 margins?

    question 
    opened by Mordil 5
  • [Accessibility] Selecting days using Voice Over does not work for custom DayViews

    [Accessibility] Selecting days using Voice Over does not work for custom DayViews

    I already testsed this with version 1.14.2 which is the newest at the moment.

    When I have a calendar with custom content for:

    • month header
    • week of day header
    • days

    the selection of days does not work anymore when I swipe through the views using Voice Over. As you can see we have days that are not selectable in the screenshot below. The first problem is:

    • as soon as I set e.g. invariantViewProperties.highlightShapeDrawingConfig.fillColor = .red the days become unselectable (not sure what the one has to do with the other)
    • when I comment this line days become selectable but also the inactive ones. It would be cool when inactive days are skipped automatically by VO. A simple solution would be to offer to set isAccessibilityElement in DayOfWeekView.InvariantViewProperties like we can do it on DayOfWeekView.InvariantViewProperties 🤓

    5C78B0DA-4466-4689-B3E9-5EC3BE1D2DA4

    opened by Blackjacx 0
  • Date range selection

    Date range selection

    Hello Team,

    First of all thats for this beautifull library. I have a issue regarding date selection i choose i date and i need to select previos date anf future date from that date for example if i choose 24 then i need to show 22 and 26 selected date. But when i pass the range to highlighted i,e 22 and 26 it only highlights 24 date here is screen shot. (https://user-images.githubusercontent.com/87111681/194310776-44198379-d858-4d4f-8483-1ca03e913247.png)

    Please give me suggestion how i can archived this.

    Thanks

    question 
    opened by Sachin-Arora123 1
  • Intrinsic content size is not being calculated

    Intrinsic content size is not being calculated

    As per #157 it states that intrinsic size is now being calculated. It doesn't seem to be working anymore. I thought I may have setup the view incorrectly but even on the demos the size is being returned with (width: -1.0, height: -1.0).

    opened by dimitryvin 0
  • Other color in new range

    Other color in new range

    Good morning! I have such a situation, there is a period in the calendar. How do I highlight the newly added period (paint it in a different color)? The documentation describes only a specific day.

    opened by PollyVern 0
  • Change the background color of the days of week header

    Change the background color of the days of week header

    Hey! First of all, great work on this library, It was super easy to build and integrate it into my app, kudos! I have a question though, is it possible to color the white spaces between the days of week objects in the view (so that the entire header structure is unicolor)? I have attached the picture for reference. Thanks!

    Screenshot 2022-06-29 at 1 53 59 AM
    opened by Sarthakh 2
Owner
Airbnb
Airbnb
Simple customizable calendar component in Swift :calendar:

Koyomi Koyomi is a simple calendar view framework for iOS, written in Swift ?? Content Features Demo App Usage introduction : Change displayed month,

Shohei Yokoyama 741 Dec 24, 2022
A fully customizable calendar view acting as a date range picker

Demo Installation CocoaPods With CocoaPods you can simply add GLCalendarView in your Podfile: pod "GLCalendarView", "~> 1.0.0" Source File You can co

Glow Inc 860 Nov 18, 2022
The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable

Q: How will my calendar dateCells look with this library? A: However you want them to look. More Images Features Range selection - select dates in a r

PatchTheCode 7.3k Jan 2, 2023
Full featured lunar calendar library

LunarCore 小历(iOS & OS X)的农历核心部分。 何为小历 小历是一个简洁的农历 app,目前支持 iOS & OS X 两端,iOS 端多次被 App Store 官方推荐。 前世今生 LunarCore 最早来自于一个 JavaScript 编写的农历库:LunarCalenda

Ying Zhong (Inactive) 782 Aug 31, 2022
An easy to use SwiftUI date picker for Shamsi (Persian) calendar

ShamsiDatePicker An easy-to-use SwiftUI iOS/watchOS date picker for Shamsi (Persian) calendar. Features Pure (100%) SwiftUI implementation Full suppor

Seyyed Parsa Neshaei 20 Nov 24, 2022
SwiftUI Simple Calendar / Date Picker for iOS

RKCalendar RKCalendar is a SwiftUI Calendar / Date Picker for iOS. Features include: minimum and maximum calendar dates selectable, single date select

null 453 Dec 28, 2022
A nicer iOS UI component for picking date and time

DateTimePicker A nicer iOS UI component for picking date and time. Features Date and Time Picker / Date Picker only / Time Picker only - your choice!

Huong Do 1.9k Jan 4, 2023
iOS 7+ Calendar (Date Picker) with Infinite Scrolling.

RSDayFlow iOS 7 Calendar with Infinite Scrolling. Only need 4 lines of code to set up. RSDayFlow is a slim fork of DayFlow with updates and extensions

Ruslan Skorb 844 Sep 14, 2022
A library that expresses a github contribution calendar through an array of dates. Supports iOS and macOS.

A library that expresses a github contribution calendar through an array of dates. Supports iOS and macOS.

jasu 45 Dec 20, 2022
A SwiftUI calendar view that allows month switching and date picking.

Selectable Calendar View A SwiftUI calendar view that allows month switching and date picking. Usage You can simply add this repository to your projec

シュンジョーァ 10 Jul 21, 2022
A fully customizable iOS calendar library, compatible with Objective-C and Swift

Table of contents Screenshots Installation Pre-knowledge Support Contact Screenshots iPhone iPad Safe Orientation Today Extension iOS8/9 iOS10 Interac

Wenchao Ding 10.2k Jan 2, 2023
Calendar component with RTL languages written in swift

GDCalendar Calendar component with both RTL/LTR languages support with Swipe Gesture enabled navigation. Easy to use with Storyboard and Attributes In

Saeid 26 Jul 17, 2022
A Swift UI component for Lunar Calendar Picker

LunarYearDatePicker a Swift UI component for Lunar Calendar Picker Usage: struct

null 6 Nov 22, 2022
Malendar is a personal calendar app that connects to your default calendar and lets you add/delete events

Malendar is a personal calendar app that connects to your default calendar and lets you add/delete events. It will gather events from your default iOS calendar.

Chase 194 Jan 4, 2023
An Easy to Use Calendar for iOS (Swift 5.0)

This is an easy to use, "just drag and drop it in your code" type of calendar for iOS. It supports both vertical and horizontal scrolling, as well as

Michael Michailidis 525 Dec 23, 2022
SwiftUICalendar - SwiftUI simple calendar

SwiftUICalendar Installation CocoaPods pod 'SwiftUICalendar' import import SwiftUICalendar Features Infinite scroll Support horizontal and vertical sc

null 59 Dec 27, 2022
Dead simple calendar implementation

Package provides a CalendarView which can be used to display simple calendar in your App.

Sergei Kononov 4 Oct 7, 2022
Calendar View - It's lightweight and simple control with supporting Locale and CalendarIdentifier.

iOS Calendar It's lightweight and simple control with supporting Locale and CalendarIdentifier. There're samples for iPhone and iPad, and also with us

Maksym Bilan 159 Dec 22, 2022
An Event View based on Apple's Event Detail View. Written in Swift 3. Supports ARC, Autolayout and editing via StoryBoard.

An Event View based on Apple's Event Detail View. Written in Swift 3. Supports ARC, Autolayout and editing via StoryBoard. Installation CocoaPods PTEv

Aman Taneja 36 Oct 5, 2022