SleepTap

SwiftUI Background Tasks after iOS 26.6

Back to product home

This is the story behind SleepTap 1.6.3: a week of missing wake dates, a minimal comparison app, and the AppDelegate registration path that finally ran.

The symptom appeared on Apple Watch

When I tapped bedtime on the Watch, it suddenly asked me to enter a wake time manually. SleepTap normally prepares seven days of wake times in advance. Opening the app once immediately rebuilt the window and synchronized it to the Watch. The date generation, foreground refresh, and Watch synchronization were working; background execution was the remaining suspect.

Waiting, restarting, and reinstalling did not help

I waited, restarted the iPhone, changed code, reinstalled the app, and raised its version. SleepTap had two App Refresh tasks at the time: one for wake dates and one for weather and sunlight notifications. Removing the weather task did not restore execution.

Disconnecting the iPhone from Xcode was not the cause. The request belongs to the iPhone’s BGTaskScheduler; the Mac and cable only provide installation, debugging, and log access.

A minimal test app failed in the same way

I made a small test app with no weather, Watch, SwiftData, or network requests. It wrote handler.entered to a local log and posted a notification when the handler ran. The request submitted successfully, but the handler was never called.

That test used the same SwiftUI registration path as SleepTap:

.backgroundTask(.appRefresh(identifier)) {
  await handleAppRefresh()
}

Two applications using the same path failing together did not prove that iOS 26.6 had stopped scheduling all background work. It showed that the registration path deserved a direct comparison.

Apple’s sample uses AppDelegate

Apple’s background-task sample registers the handler in application(_:didFinishLaunchingWithOptions:) with BGTaskScheduler.shared.register. An Apple Developer Forums discussion also points to registering early through an app delegate. My iOS 26.6 symptom was different, so I cannot claim that the issues are the same bug. They do point to the same risky boundary: a SwiftUI registration may happen too late.

earliestBeginDate is only a lower bound, not an appointment. Several days without a handler entry, a seven-day wake window running out, and a minimal test app with the same result made the evidence stronger than a normal scheduling delay.

The AppDelegate comparison worked

I changed the test app to register through AppDelegate while keeping its identifier, notification, and event log. The task became eligible at 14:05:53, entered at 14:09:09, posted its notification, and recorded completion.

This was not a perfect single-variable experiment because I also changed the first delay. The old SwiftUI request had already remained unhandled for hours and days, while the AppDelegate version completed on its first natural run. That was enough evidence to apply the same comparison to SleepTap.

The SleepTap change

SleepTap 1.6.3 now registers its background handler early through AppDelegate. It keeps one system background task: after it runs, SleepTap renews the next request, updates sleep notifications and the Watch wake-time window, and refreshes weather and sunlight notifications when needed.

Weather keeps its existing rules: it works during the 6:00–18:00 window, checks the six-hour cache and location changes, and refreshes only when necessary. The point was to reduce variables in one wake-up path, not to introduce a new architecture.

What I can and cannot conclude

On my iPhone SE (3rd generation) running iOS 26.6, two apps using SwiftUI .backgroundTask(.appRefresh(...)) did not naturally enter their handlers. After the test app switched to direct BGTaskScheduler.register registration in AppDelegate, it ran successfully.

I cannot conclude that every iOS 26.6 device is affected, or that this is exactly the same bug discussed for iOS 18.4. If App Refresh stops after an OS upgrade while submission still succeeds, record handler.entered, build a minimal comparison app, and compare SwiftUI registration with early AppDelegate registration.