Published on

UICalendarView Tutorial

At WWDC ’22, Apple announced new controls to UIKit. In this tutorial, we will dive into one of the most useful and versatile component- UICalendarView.

What is UICalendarView

The inline date-picker component of UIDatePicker is now available as a separate component. A calendar view is used to show users specific dates along with additional information and decoration for those specific dates. For example, in the Calendar app, the dates that have events are marked with a pinkish dot below the date. A calendar view can also be used to select one or multiple dates, or no dates at all. You can pre-set dates, disable certain dates from being selected, and more with UICalendarView.

How is it different from a UIDatePicker?

A UIDatePicker allows the user to select just one point in time. This selection is singular and cannot be used to select a range of, or multiple dates. A date picker must still be used if you’d like to get input from the user about a specific point in time, such as the date for which a transaction has to be registered. You use a calendar view only for the display and selection of dates. If you want to handle date and time selection, use UIDatePicker.

Considerations

  • An important difference between UICalendarView and UIDatePicker is that UICalendarView represents dates as NSDateComponents in contrast to UIDatePicker which represents a particular point in time using NSDate.
  • Since UICalendarView’s dates are represented by NSDateComponents, it is our responsibility to be explicit about the current calendar that we want to use. We cannot assume that, for example, the Gregorian calendar will be set by default. UICalendarView will configure itself with the system default calendar depending on the user’s current set calendar if a calendar is not passed explicitly.
Screenshot of Xcode showing the various available system calendars

Here we can see a list of available calendars. We must be explicit in which calendar we’d like to use for our input.

Getting started

To create a calendar view, we create an instance of UICalendarView and set its calendar. We can then add it to the view using auto-layout constraints.

let calendarView = UICalendarView()
calendarView.calendar = Calendar(identifier: .gregorian)
calendarView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(calendarView)
NSLayoutConstraint.activate([
    calendarView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
    calendarView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
    calendarView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

Setting date range

UICalendarView has a method to set the visible date range. This range set can also be animated to show changes in response to a user input or a runtime conditional change. To set visible date components, use this method

func setVisibleDateComponents(
    _ dateComponents: DateComponents,
    animated: Bool
)
// calendarView.setVisibileDateComponents(..., animated: true)

If the supplied date components are not in the same calendar as the UICalendarView, the input date components will be converted to use UICalendarView.calendar upon assignment. This may result in date mismatch and might cause invalid dates if the two calendar systems don’t have the same dates.

Handling Date Selections

We can set a selection behaviour property on our calendar view to define what kind of input it will accept. We can configure it with single selection and multi selection. In this example, we are setting a multi-date selection.

let multiDateSelection = UICalendarSelectionMultiDate(delegate: self)

There is another selection method available called UICalendarSelectionSingleDate that is used to select single dates only.

The delegate of a UICalendarSelectionMultiDate must conform to UICalendarSelectionMultiDateDelegate. This delegate has 2 required methods and 2 optional methods.

Required Methods

  • The first method is didSelectDate and its function signature looks like this.
func multiDateSelection(_ selection: UICalendarSelectionMultiDate, didSelectDate dateComponents: DateComponents)

This method is called after the user selects a date in the calendar view.

  • The other required method is didDeselectDate and its function signature looks like this.
func multiDateSelection(_ selection: UICalendarSelectionMultiDate, didDeselectDate dateComponents: DateComponents)

This method is called after the user removes selection from one of the selected dates the calendar view.

Optional Methods

  • The first optionally available method is canSelectDate.
optional func multiDateSelection(_ selection: UICalendarSelectionMultiDate, canSelectDate dateComponents: DateComponents) -> Bool

This call determines if a date is selectable. Dates that are not selectable will be disabled in the calendar view.

  • The second optional delegate method is canDeselectDate.
optional func multiDateSelection(_ selection: UICalendarSelectionMultiDate, canDeselectDate dateComponents: DateComponents) -> Bool

This call determines if a date can be deselected.

Customisation

UICalendarView lets us provide decorations for a date similar to what you might see in the system calendar app. This component has a wantsDateDecorations that is set to true by default. However, you have to implement the delegate method to supply these decorations. This is what we will be looking at in this sub-section. Set calendarView’s delegate to self and conform your view-controller to UICalendarViewDelegate.

calendarView.delegate = self
extension ViewController: UICalendarViewDelegate {}

This conformance requires us to implement one required method.

func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration?

UICalendarView.Decoration

A decoration has 3 different types. Regular, Image, and Custom view.

Regular

The default init() creates a default decoration with a circle image as seen in the system calendar app.

With Image

This initialiser accepts an UIImage, a UIColor, and a UICalendarViewDecorationSize and creates a new image-based decoration with the specified image, color, and size.

  • The image defaults to circlebadge.fill if no image is passed explicitly.
  • The color defaults to UIColor.systemFillColor if no color is passed explicitly.
  • The size defaults to UICalendarViewDecorationSizeMedium if no size is passed explicitly.

With Custom View

The Apple documentation explains this well.

Creates a new custom view decoration using the provided view provider. The provider will be called once when the decoration view is first loaded. The decoration will be clipped to its parent’s bounds, and cannot have interaction.

This initialiser accepts a UIView and sets that as the calendar’s date’s decoration subject to the aforementioned restrictions.

UICalendarView.DecorationSize is an enum representing the available sizes for the decoration item.

UICalendarViewDecorationSizeSmall = 0,
UICalendarViewDecorationSizeMedium = 1,
UICalendarViewDecorationSizeLarge = 2,

To set a decoration view, simply provide the required decoration in the delegate method. For example. from the What’s New in UIKit session from WWDC ’22:

// Configuring Decorations
func calendarView(
    _ calendarView: UICalendarView,
    decorationFor dateComponents: DateComponents
) -> UICalendarView.Decoration? {
    switch myDatabase.eventType(on: dateComponents) {
    case .none:
        return nil
    case .busy:
        return .default()
    case .travel:
        return .image(airplaneImage, color: .systemOrange)
    case .party:
        return .customView {
            MyPartyEmojiLabel()
        }
    }
}

You can reload the decorations during runtime by calling this method.

reloadDecorations(forDateComponents:animated:)

This method accepts an array of DateComponents denoting the dates for which the decoration must be reloaded. You can even choose to animate these changes.

Conclusion

The new UICalendarView is a powerful component to select single and multiple dates as compared to a single point in time as provided by UIDatePicker. These two components have their own use-cases and shouldn’t be viewed as substitutes of either. Use a UIDatePicker when you want to select a point in time. Use the new calendar view when you want to choose a single or a range of dates. A calendar view accepts DateComponents vs a date picker that accepts a NSDate.

Thank you for reading!