SwiftUI Profiling with Instruments: 100,000 Calendar Events

A 100,000-event stress test sent us through SwiftData, SwiftUI layout, navigation caches, and a surprisingly expensive hover effect.

6 min read
Instruments profiling a calendar interaction in hora Calendar

We put 100,000 events into hora Calendar's local database to see how it handled a calendar much larger than a typical account.

The current app feels smooth in our manual checks. Earlier builds had noticeable pauses when navigating busy calendars. Getting from one to the other took several rounds of SwiftUI profiling with Instruments, changes to the data and rendering paths, and one surprisingly ordinary decision: removing the visual hover effect from event blocks.

Hover was an expensive detail in our implementation. It was also part of a bigger problem: too much work happening around the events already on screen.

What our 100,000-event test actually means

We have a dedicated large-dataset mode that can generate 30,000, 50,000, or 100,000 events in an isolated local store.

The calendar still loads the relevant date range and displays the appropriate events. We are testing a large database and busy views, not drawing 100,000 event blocks at once.

That distinction matters. A large history stresses the data path. A dense week stresses the view hierarchy, layout, and interactions. We needed to understand both.

hora Calendar displaying a week from the isolated 100,000-event test database
hora Calendar displaying a date range from the isolated 100,000-event test store.

For background on the product, I wrote about why I built a native Google Calendar app for Mac.

What Instruments showed us

One early diagnostic recording lasted 39.1 seconds and contained 11 hangs or microhangs at a 250 ms threshold, plus 243 recorded hitches.

The longest hang was about 762 ms. In that interval, roughly 577 ms of sampled main-thread work included SwiftUI graph updates. SQL fetch stacks on the main thread accounted for only about 0.5 ms of samples.

Those numbers describe one recording. They are not average query timings or a controlled before-and-after benchmark. It was a Debug capture with Layout Updates enabled, which added diagnostic overhead.

Even with that qualification, the trace was useful. During the stalls we examined, the main thread was busy updating the interface. Improving database reads alone would leave a substantial part of that work untouched.

Apple's SwiftUI performance guidance makes the same distinction between updates that take too long and updates that happen too frequently. Both are worth investigating.

Instruments showing a selected calendar hang and the corresponding main-thread call stacks
An early diagnostic recording showing main-thread work during a calendar hang.

Why we removed the event hover effect

The visual effect was small: move the pointer over an event and the block becomes a little brighter.

The implementation around it was less small. Event tiles carried interaction state and modifiers alongside presentation work. Across a dense calendar, that added tracking and update work for a detail that did not change the underlying event.

We changed this in stages.

First, event tiles received value snapshots rather than reading their presentation directly from persistence models. Then pointer tracking moved to the day-column layer, with the heavier interaction modifiers mounted for the active event. Unchanged tiles could also skip redundant work through an explicit equality comparison.

Finally, we removed the visual hover effect from the shared event-popover modifier and the extra brightness effect on timed event blocks.

Clicking an event still opens its details. The calendar retains its context menus, drag, and resize paths. Removing the decorative effect gave us a simpler interaction path to maintain.

This was specific to our implementation. A hover callback can be perfectly reasonable. What matters is the state, dependencies, and work connected to it.

The other changes that helped

The commit history tells a broader story than one removed modifier.

Calendar reads and presentation preparation moved into background snapshots. Those snapshots include the day buckets, overlap columns, and other values the interface needs.

We narrowed the rendered viewport and reused its selection for tiles and accessibility. The visible tile layout became simpler, and the hour grid moved to one Canvas per day column.

Navigation also needed attention. The cache now survives changes between Day, Week, and Month, and prepares adjacent ranges after a cache hit. We removed fixed waits from the foreground and prefetch paths.

Month view had its own costs, including extra maintained pages and delayed event reveal. Sidebar transitions could repeatedly change the calendar's width, triggering layout work at intermediate sizes.

Each change addressed a different way of repeating or delaying work. Together, they made everyday navigation feel much better.

Instruments had its own performance problem

The profiling tool was demanding too.

On our MacBook Pro with an Apple M5 and 16 GB of RAM, recordings longer than roughly 50 seconds could leave Instruments processing for around an hour before crashing.

That was our experience with these captures, rather than a general recording limit. Recording options mattered: Layout Updates exposed useful detail but also produced a lot of data. One raw layout-table export had already exceeded 1.9 GB after covering only about 3.4 seconds of a recording, so we stopped that export.

Xcode 27 was the first version where we could get usable SwiftUI profiling for this investigation. Earlier attempts had not given us a reliable view of the problem.

Apple introduced the new SwiftUI instrument in Xcode 26. Apple's introduction to the SwiftUI instrument covers that release. The version where it became available and the version where our particular investigation became workable were different.

How I would approach SwiftUI profiling next time

I would start with one interaction: scrolling a busy week, moving between dates, or toggling a sidebar.

Use Product > Profile in Xcode, select the SwiftUI template, and record that interaction. Check Hangs and Hitches, then inspect the affected interval in SwiftUI and Time Profiler. Use the Cause & Effect graph when you need to understand why views update. Apple's profiling walkthrough explains the workflow.

A diagnostic capture helps locate work. For an actual performance comparison, use an optimized build and keep the data, window size, display, and interaction sequence consistent. Our recordings changed some of those conditions, so we are not attaching a percentage speedup to this work. Apple's session on app responsiveness covers the importance of profiling an optimized build.

I learned a similar lesson while fixing SwiftUI appearance switching on macOS: a convincing first explanation still needs evidence from the actual view hierarchy.

Where hora is now

This work left us with a better separated SQL and SwiftData path, less repeated presentation work, and a calendar that feels much smoother in our current manual testing, including the 100,000-event case.

In day-to-day use, we no longer feel that hora trails Fantastical or BusyCal in responsiveness. That is our assessment of using the apps, rather than a comparative benchmark.

There will be another edge case. But we now have a much clearer way to investigate it: trace the interaction through data loading, presentation preparation, and the view update it actually triggers.

If you use Google Calendar and want to try the result, download hora Calendar for Mac.

Related stories

Native vs Electron vs PWA for Mac Apps
Engineering

Native vs Electron vs PWA for Mac Apps

Native, Electron, or PWA for a Mac app? A practical comparison of runtime, OS integration, performance, and the tradeoffs behind hora Calendar.

Maciej Szamowski10 min read
How I Fixed SwiftUI Color Contrast with OKLCH and APCA
Engineering

How I Fixed SwiftUI Color Contrast with OKLCH and APCA

A TestFlight screenshot exposed unreadable calendar tiles. Here is how I rebuilt hora's SwiftUI color system with OKLCH, APCA-style checks, and tests.

Maciej Szamowski10 min read
How I Fixed SwiftUI Appearance Switching on macOS
Engineering

How I Fixed SwiftUI Appearance Switching on macOS

The real fix for a stale SwiftUI macOS window combined an explicit color scheme, adaptive styles, and a safer NSVisualEffectView bridge in hora.

Maciej Szamowski7 min read
Mobile Beta Sign-up

Want to stay updated?

Subscribe for information about iOS/iPadOS beta availability.