If you've ever tried to build an iOS app that intercepts another app's launch, a custom launcher, a Screen Time blocker, a digital wellbeing tool, you've probably studied One Sec.
To intercept an app launch seamlessly, One Sec asks users to set up an Apple Shortcuts Automation: "When Instagram is opened -> Run One Sec."
It works beautifully. But the moment you try to code this yourself, you hit an immediate brick wall:
The Deeplink Infinite Loop
When your app finishes its logic and sends the user back to Instagram via a deeplink (instagram://), the Shortcut Automation detects the "App Opened" event again. It fires instantly, yanking the user right back into your app.
I searched around for answers on Reddit, Stack Overflow, Apple Developer Forums and others, and it seems a lot of developers in the past have attempted this and eventually admitted defeat.
Most existing solutions in this space predate iOS 26 and rely on some variant of these workarounds rely on some flow breaking sacrifices just to get working.
I'm an Electrical Engineer by trade but I enjoy making software tools that I genuinely intend to use, and I recently built a free, open-source alternative to One Sec that forces a video game like loading screen before distracting apps that I was using far too often. One Sec was able to achieve this so I knew it must be possible.
I spent days stuck on this. The solution finally clicked when I stumbled across a comment on r/swift by u/Extreme-Baby3813 pointing out that in iOS 26 Apple added .foreground(.dynamic) to their App Intents framework. That was the missing piece.
Honestly, I have no idea how One Sec was doing this before iOS 26 but thanks to the new .foreground(.dynamic) there is a relatively straight forward solution.
The Solution: Background State Checks via App Intents
To break the loop, you have to intercept the Shortcut automation before it pulls your app into the foreground. You do this by exposing a custom App Intent to the Shortcuts app and using a shared App Group to manage state across processes.
Here's the flow:
User taps Instagram
|
Shortcut Automation fires LaunchWithBootUpIntent (in the background)
|
Intent checks the "Launch Pass" in the App Group
├─ Pass exists -> return .result() silently -> Instagram opens normally
└─ No pass -> continueInForeground() -> trigger the loading screen
The loop is broken by a single boolean check that happens entirely in the background.
Step 1: The "Launch Pass" State
When the user successfully completes their wait in your main app, drop a temporary "Launch Pass" token into a shared App Group before deeplinking them back to the target app:
// right before calling open(instagram://)
let sharedDefaults = UserDefaults(suiteName: "group.com.yourname.AppData")
sharedDefaults?.set(true, forKey: "LaunchPass_com.instagram.instagram")
You'll want to make this a bit smarter than a plain bool, in production I store an expiry timestamp keyed by bundle ID so passes auto-expire after the grace period, but a bool is enough to illustrate the concept here.
Step 2: The Loop-Breaking App Intent
This is the intent the user actually selects in the Shortcuts app (instead of a generic "Open App" command):
import AppIntents
import Foundation
@available(iOS 26.0, *)
struct LaunchWithBootUpIntent: AppIntent {
static let title: LocalizedStringResource = "Launch with Boot Up"
// The intent starts in the background and only escalates if it needs to
static var supportedModes: IntentModes = [.background, .foreground(.dynamic)]
@Parameter(title: "Target App")
var targetApp: BootUpAppEntity
init() {}
init(targetApp: BootUpAppEntity) { self.targetApp = targetApp }
func perform() async throws -> some IntentResult {
let bundleID = targetApp.bundleID
let data = SharedDataManager.shared
// Check the shared App Group
// If the user has a valid Launch Pass, return silently
if data.consumeLaunchPass(forBundleID: bundleID) {
return .result()
}
// user is trying to open the app fresh
// Build the deeplink back into our own app
let urlString = "bootup://launch?bundle=\(bundleID)"
guard let url = URL(string: urlString) else { return .result() }
// bring from background to foreground
if systemContext.currentMode.canContinueInForeground {
do {
try await continueInForeground(alwaysConfirm: false)
} catch {
return .result()
}
}
// trigger the custom loading screen in the main app
await MainActor.run {
NotificationCenter.default.post(
name: .bootupLaunchURL,
object: nil,
userInfo: ["url": url]
)
}
return .result()
}
}
The key line is supportedModes: IntentModes = [.background, .foreground(.dynamic)].
.background tells iOS the intent is allowed to run without ever bringing your app to the foreground. .foreground(.dynamic) tells it the intent may escalate to the foreground later, based on runtime conditions. Together they give us the ability to start invisible, decide what to do, and only surface the UI when you actually need to.
How It Plays Out
Here's the actual user-visible behavior:
Case 1 — Returning to the app after a successful intercept
- User completes the loading screen in your app.
- Your app grants a Launch Pass and calls
open(instagram://). - iOS switches to Instagram. The "App Opened" Shortcut automation fires.
-
LaunchWithBootUpIntent.perform()runs entirely in the background. - It sees the Launch Pass, consumes it, returns
.result(). - Instagram opens normally. The user never saw your app appear. The infinite loop is broken without a single frame flashing on screen.
Case 2 — Fresh launch attempt
- User taps Instagram from the home screen.
- The "App Opened" Shortcut fires.
-
LaunchWithBootUpIntent.perform()runs in the background. - No Launch Pass exists.
- The intent calls
continueInForeground(), then posts thebootup://URL. - Your main app receives the URL via
NotificationCenterand renders the loading screen. Same intent, same code path, two completely different outcomes — chosen by a single state check before any UI work happens.
Caveats Worth Mentioning
- This requires iOS 26. If you need to support earlier versions you'll need a fallback path.
- The user still has to manually set up the Shortcut automation once per app. There is no good way to create these automations programmatically. (let me know if you figure this out please, I'll give you a kiss)
See It in Production
I built this whole thing for an app called Boot Up, a free, open-source iOS app that intercepts distracting apps with a video game style loading screen.
I didn't want to pay $100 for an app that ultimately makes my phone slower so I decided to try it in my own style. Boot Up is currently in TestFlight beta and I'd love your feedback:
The full implementation is here, including the DeviceActivityMonitor extension that auto-relocks apps after a configurable grace period, the ShieldConfiguration extension for the lock screen UI, and the rest of the architecture this article only touches on.
Source code: github.com/eliguzz/BootUp
Thanks for reading!
Top comments (0)