Hi,
I am building an iOS app that uses FamilyControls / ManagedSettings to restrict apps.
Flow of my app:
- In my main app, the user chooses which apps to restrict using
FamilyActivityPicker(for example, they select Instagram). - I save the selection in an App Group.
- I then use
ManagedSettingsStorein the main app to add those app tokens intostore.shield.applications, so a Screen Time shield appears when the user opens Instagram. - In my ShieldConfigurationExtension, I show a shield UI with a primary button called “Access App”.
- In my ShieldActionExtension, when the user taps “Access App”, I want to immediately hide the shield and allow Instagram.
To hide the shield, I am using this code in my ShieldActionExtension:
final class ShieldActionExtension: ShieldActionDelegate {
// ...
override func handle(
action: ShieldAction,
for application: ApplicationToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
switch action {
case .primaryButtonPressed:
handlePrimaryButton(for: application, completionHandler: completionHandler)
case .secondaryButtonPressed:
completionHandler(.close)
@unknown default:
completionHandler(.defer)
}
}
private func handlePrimaryButton(
for application: ApplicationToken,
completionHandler: @escaping (ShieldActionResponse) -> Void
) {
// (I update some app-group state here, lives, history, etc.)
// This is the important part: I try to unshield the app
let store = ManagedSettingsStore()
var apps = store.shield.applications ?? Set<ApplicationToken>()
apps.remove(application)
store.shield.applications = apps
// I then tell the system to re-evaluate
completionHandler(.defer)
}
}
(When testing another approach, I also tried completionHandler(.close) after removing the app from the shield applications.)
Behavior I see:
-
Local / Xcode debug build (installed by cable):
- Open Instagram → Slofy shield appears.
- Tap “Access App” → the above code runs.
- Shield disappears immediately and Instagram is usable. ✅
-
TestFlight build:
- Open Instagram → Slofy shield appears.
- Tap “Access App” → the above code runs, and I see in logs:
Removed app from shield set (apps now: 0) - But the shield does not hide. It stays on the screen. ❌
- Only if I then open my main app (Slofy) and close it again, and then return to Instagram, the shield disappears and Instagram is unlocked.
So the same code works as expected in local debug builds, but in TestFlight builds the Screen Time shield does not refresh / disappear immediately after I remove the app from store.shield.applications inside the ShieldActionExtension.
My questions:
- Is it supported to unshield an app directly from inside a
ShieldActionExtension(by removing it fromManagedSettingsStore().shield.applications) and expect the shield to disappear immediately? - Is there any difference in how
ManagedSettingsStorechanges are applied between debug and TestFlight / release builds for Screen Time shields? - Is the main app required to be in the foreground for the shield to update, or is there a recommended pattern to make the shield hide right after the user taps the primary button in the shield?
I would like the behavior to be:
User opens restricted app → shield shows → taps “Access App” → shield hides immediately and the app becomes usable, without needing to open the main app.
Any guidance on the correct way to implement this with Screen Time extensions would be greatly appreciated.
Thank you.