The new UICalendarView added to UIKit in iOS 16 looks great but there’s not a SwiftUI equivalent. Here’s how I got a SwiftUI app to show the calendar based on custom dates and update the calendar when dates change.

You can just jump to the code if that’s what you’re interested in.

A new calendar tool!

I attended WWDC in person this year and I was so excited when I saw UICalendarView being demoed in the presentations. Like my friend Pitt I had also created my own custom calendar implementation for my current side project.

I actually couldn’t believe my good luck because my calendar was so similar to what they were showing. The version I had come up with would show an emoji instead of the day on populated days.

The month of June with boba tea emojis covering several days.

As you can see in Pitt’s Tweet above the Apple implementation shows an emoji under the day (instead of covering the day), which I like better. Using a built-in, Apple version of a calendar seems like a no-brainer in my situation. I was struggling with getting mine to look not-hideous with the largest accessibility fonts on the smallest screens. Apple makes accessibility a priority so you know that they’re going to handle that scenario for you. My excitement was tempered when I realized that this was an addition to UIKit and there was no SwiftUI equivalent (yet?).

Only for UIKit

As someone who has been focused on SwiftUI 100% for the last two years it’s the understatement of the decade to say that I’m rusty in UIKit. Still, this Tweet from Majid made me want to jump into get this working in my SwiftUI app.

It’s ironic that Majid posted that Tweet because the calendar View I had come up with before WWDC used some of his publicly available code (that I asked if I could use) as a starting point. I then made many modifications to it.

Jordan Morgan also made an early blog post about UICalendarView and he showed how to populate emojis on every day.

func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
	return .customView {
    	let emoji = UILabel()
        emoji.text = "🚀"
        return emoji
    }
}

Another complication is that UICalendarView deals with DateComponents and not Date like I’m used to working with. It required some research and experimentation to understand how DateComponents work.

Armed with this information it wasn’t long before a had a rudimentary SwiftUI app displaying a UICalendarView.

Reading info for individual days

Now it was time for the next step, which was to display different emojis for different days based data that was processed. This was my first stumbling block.

The Apple Developer website didn’t have any sample code for UICalendarView.

Sample Code. View sample code referenced in the WWDC22 session videos. UICalendarView. No Results.

In Jordan’s example we were having calendarView() always return the same decoration for each day. Now it was time to read a day’s data and display a decoration based on that. Here’s what the developer documentation listed for implementing that.

class MyEventDatabase: NSObject, UICalendarViewDelegate {
    // Your database implementation goes here.
    func calendarView(_ calendarView: UICalendarView, decorationFor: DateComponents) -> UICalendarView.Decoration? {
        // Create and return calendar decorations here.
    }
}

let database = MyEventDatabase()
calendarView.delegate = database

I needed to write code for the “Your database implementation goes here.” How would I go about doing that? And wouldn’t the implementation for a UIKit app be different from a SwiftUI app? If there’s anyone even remotely connected to Apple documentation reading this I’m begging you to provide code examples in your documentation. With this documentation I have absolutely nothing to go on. Even if it was a painfully basic example that would never make it into a production app it would still allow the reader to understand the foundation of how this feature works. I wish that my first experience with UICalendarView would have been excitement at trying it instead of frustration with trying to figure out how to use it.

In a regular scenario for me I would have my data in a @StateObject that I would either inject into the environment or pass to a View. Something that was throwing me off about the “Your database implementation goes here.” comment is that I didn’t understand if it was saying it had to be declared there or it just meant it would be referenced here. But if it was declared here how would my other SwiftUI Views reference it?

I definitely needed a refresher on delegates and coordinators since I hadn’t dealt with them since the 100 Days of SwiftUI. Pretty soon after that I had created a “database” that the calendar was reading from and different days were displaying their appropriate emoji. I still wasn’t sure if my “database” was implemented correctly

Now the next step would be allowing the user to change or delete days. This ended up being my biggest stumbling block of all. Here’s what the documentation says about reloading decoration for days.

database.observeEventChanges { changedDates in 
    calendarView.reloadDecorations(for: changedDates, animated: true)
}

Doing a Google search and a search of the documentation for “observeEventChanges” returns nothing so I assumed that this was a custom solution the author came up with. Again, wouldn’t a UIKit implementation differ from a SwiftUI one? Normally in a SwiftUI app I’d have my “database” in an @Published property wrapper, which would observe state changes. Since it would be in an ObservableObject I also would have access to objectWillChange.send() that I could trigger when I make a change. I really wasn’t sure if that was what I was supposed to be emulating in this class for the calendar. I was pretty sure the call to reload decorations needed to go in the updateUIView() function in the UIViewRepresentable but I wasn’t sure how to implement it. Again, some sample code would have been a world of help.

I’m stuck

It’s an understatement to say that I was really stuck at this point. I had been Tweeting about my attempts to get all of this working and Amro Mousa was kind enough to offer to look at my code and help me. Even though he tried to be modest and downplay his SwiftUI skills he had me fixed almost immediately. Let’s take a look at the changes he made.

Cleaning up my “database”

I had tried so many different approaches and had moved my “database” around so much that I ended up having some duplicate data that was causing issues. Here’s how the version Amro cleaned up handles passing the data.

My @StateObject that is injected into the environment is accessed in ContentView via @EnvironmentObject .

struct ContentView: View {
    @EnvironmentObject var allData : AllData
    @State var dateSelected : DateComponents?
    @State var showSheet = false
    
    var body: some View {
        CalendarView(interval: .init(
            start: Calendar.current.date(from: Calendar.current.dateComponents([.year, .month], from: Calendar.current.startOfDay(for: Date())))!, end: .now
        ), allData: allData, dateSelected: $dateSelected, showSheet: $showSheet)
        .sheet(isPresented: $showSheet) {
            TheSheet(dateSelected: $dateSelected)
                .presentationDetents([.medium, .large])
        }
    }
}

It’s passed to my custom CalendarView as an @ObservedObject.

struct CalendarView : UIViewRepresentable {
    let interval : DateInterval

    @ObservedObject var allData : AllData
    @Binding var dateSelected : DateComponents?
    @Binding var showSheet : Bool
    
    func makeCoordinator() -> Coordinator {
        // Create an instance of Coordinator
        Coordinator(self, allData: _allData, dateSelected: $dateSelected, showSheet: $showSheet)
    }

And finally Amro passed it from there to the Coordinator class also as an @ObservedObject .

class Coordinator: NSObject, UICalendarViewDelegate, UICalendarSelectionSingleDateDelegate {
    var parent: CalendarView

    @ObservedObject var allData : AllData
    @Binding var dateSelected : DateComponents?
    @Binding var showSheet : Bool
    
    init(_ calendarStuff: CalendarView, allData : ObservedObject<AllData>, dateSelected: Binding<DateComponents?>, showSheet: Binding<Bool>) {
        parent = calendarStuff
        self._allData = allData
        self._dateSelected = dateSelected
        self._showSheet = showSheet
    }

My first thought was, “Couldn’t the CalendarView struct just access the environment variable itself instead of having it passed to it? But trying that gives a compile error. I guess not!

Cannot convert value of type 'EnvironmentObject<AllData>' to expected argument type 'ObservedObject<AllData>'

Observing database changes

I was trying to make observing date changes way too complicated. My @StateObject uses an an array to hold the information about each individual day. Amro just added another @Published property that’s an optional that will store information about the individual day that is changed or deleted.

    @Published var calendarData = [MyData]()
    @Published var changedData: MyData?

When we remove or change a day we store that day’s info in changedData.

// In addition to removing the element we also set it on the model
// so we can update the UI in the coordinator
allData.changedData = allData.calendarData.remove(at: foundIndex)

And then as I thought, the key to making this happen would be inside of updateUIView(). If the optional is not nil then Amro calls reloadDecorations() via the uiView property and passes it the DateComponent for the changed day. I’m honestly annoyed with myself for not trying this.

    func updateUIView(_ uiView: UIViewType, context: Context) {
        // If an element was removed, we'll tell the calendar to update the relevant cell and set the element to nil on the model so we don't try to do this again inadvertently
        if let removedData = allData.changedData {
            uiView.reloadDecorations(forDateComponents: [removedData.dateStuff], animated: true)
            allData.changedData = nil
        }
    }

Note: for my app I want the user to only be able to select one day at a time. However, UICalendarView does allow you to specify that multiple dates can be selected.

It finally works!

With Amro’s changes in place I finally had a working calendar. Stored days display their decorations and existing days can be changed or deleted. Obviously in a “real” app you’d want the user to be able to select and modify unpopulated days. But this experiment has shown that we’ll be able to do that. I can’t express how grateful I am to Amro.

The best way…

I have to admit that it felt odd to see a slide on the giant screen at WWDC that said “The best way to build an app is with Swift and SwifUI” and then discover that UICalendarView doesn’t have a SwiftUI equivalent. Either way it’s a great new tool and I’m excited to use it.

The best way to build an app is with Swift and SwifUI

Hopefully we’ll get a SwiftUI equivalent next year and I can rip out this code.

The code

Here’s the full code I put on Github so that you can view it yourself. I purposely didn’t go into detail on how delegates and coordinators work because there’s plenty of existing blog posts on other websites that have covered that. I kept searching but I couldn’t find a single thing about how to get a SwiftUI app to load UICalendarView dates, observe changes to dates, and reload calendar decorations for those dates. That prompted me to write this post. I hope the code and this post will save time and frustration for someone.

Update Sept 1, 2022

When updating my app for proper VoiceOver support I realized that VoiceOver was not reading the decoration aloud. These are the options I tried:

I posted an inquiry and got a response back that this is a bug being tracked internally. I’m running Xcode 14 beta 6.

Conclusion

If this post was helpful to you I’d love to hear about it! Find me on Twitter.