I'm encountering what appears to be a specific precedence behavior with ManagedSettingsStore.shield and would appreciate some further clarification.
My current understanding is that category-level shields take precedence over individual app allowances.
My test involved...
Using FamilyActivityPicker to select
a single target application (e.g., "Calculator," which falls under the "Utilities" category).
Using FamilyActivityPicker again to select
the category of that target application.
I applied shields using ManagedSettingsStore (named .individual):
store.shield.applicationCategories = .specific(Set([utilitiesCategoryToken]))
store.shield.applications = Set([calculatorApplicationToken])
Result:
The calculator app remains shielded, suggesting that the category-level shield on Utilities overrides the attempt to allow the individual app. I also tried this using a single picker, but received only the category token instead of all application tokens in that category.
Is this observed precedence (where store.shield.applicationCategories effectively overrides store.shield.applications for apps within the shielded category) the intended behavior?
If so, are there any mechanisms available within the main app's capabilities (potentially using a Device Activity Report Extension or Shield Extension) to allow a specific ApplicationToken if its corresponding ActivityCategoryToken is part of the store.shield.applicationCategories set?
Essentially, can store.shield.applications be used to create "allow exceptions" for individual apps that fall into an otherwise shielded category?
Additionally, I mentioned that selecting an entire category in the picker only returns the opaque category token, not any application tokens. Is there any way in which I could return both the category and all application tokens by just selecting the category?
Any insights or pointers would be greatly appreciated!
Family Controls
RSS for tagPrevent access to the Screen Time API without guardian approval and provide opaque tokens that represent apps and websites.
Posts under Family Controls tag
194 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello,
I think it is quite a common use-case to open the parent app that owns the ShieldActionDelegate when the user selects an action in the Shield.
There are only three options available that we can do in response to an action:
ShieldActionResponse.none
ShieldActionResponse.close
ShieldActionResponse.defer
It would be great if this new one would be added as well:
ShieldActionResponse.openParentApp
While finding a workaround for now, the problem is that the ShieldActionDelegate is not a normal app extension. That means, normal tricks do not work to open the parent app from here.
For example, UIApplication.shared.open(url) does not work because we can’t access UIApplication from the ShieldActionDelegate unfortunately.
NSExtensionContext is also not available in the ShieldActionDelegate unfortunately, so that’s also not possible.
There are apps however, that managed to find a workaround, in my research I stumbled across these two:
https://apps.apple.com/de/app/applocker-passcode-lock-apps/id1132845904?l=en-GB
https://apps.apple.com/us/app/app-lock/id6448239603
Please find a screen recording (gif) attached.
Their workaround is 100% what I’m looking for, so there MUST be a way to do so that is compliant with the App Store guidelines (after all, the apps are available on the App Store!).
I had documented my feature request more than 2 years ago in this radar as well: FB10393561
For an iOS app that runs in both child and parent mode across iOS devices. On the child device, with Family Controls enabled using .child permissions via AuthorizationCenter.requestAuthorization(for: .child).
Is any way to display a list of recently used apps by the child on the parent (guardian) device, in a privacy-preserving and Apple-compliant way?
Cannot convert value of type '[ApplicationToken]' (aka 'Array<Token>') to expected argument type 'Binding'
Hey folks!
In one of our apps we're using the FamilyControls framework to manage the screentime for the child. The app requires the Guardian to activate a subscription on his/her device to enable the functionality for the Child on the child device.
We're currently using StoreKit 2 to get notified when there is an active subscription. The issue we're seeing (and our users) is that an activated subscription isn't instantly propagated to the Family members, in this case the device belonging to the child.
Is this a known bug, and are the possible any workarounds? Currently we have to ask our users to "Restore Purchase" or wait minutes/hours for the app to active, which isn't a viable solution. We want it to "just work" :)
Sincerely,
César Pinto Castillo
Ambi Studio
An error was reported when requesting permissions on devices with iOS 16.2 16.3. It is not an emulator.
Through the log records, the following Error message appears
Error Domain=FamilyControls.FamilyControlsError Code=3 "(null)"
Error Domain=FamilyControls.FamilyControlsError Code=4 "(null)"
Error Domain=FamilyControls.FamilyControlsError Code=5 "(null)"
func requestScreenTime() async -> Bool {
do {
try await AuthorizationCenter.shared.requestAuthorization(for: .individual)
return AuthorizationCenter.shared.authorizationStatus == .approved
} catch {
print("\(error)")
return false
}
}
I have been approved for family controls entitlements and see them in my apple certificates for my bundle identifier. I see them listed for Distribution in my Runner, however, when I try to distribute I get the error that I do not have the right permissions. Do I need to get the entitlements for each extension I have as well? I have three extensions that use the family controls.
Hi everyone,
We're using the react-native-device-activity package to implement app blocking via Apple's Screen Time API. The blocking functionality works well: when the user selects apps and taps "Done," those apps get blocked as expected.
However, we're facing an issue with unblocking apps that the user later unselects. Even after the user unchecks some apps and taps "Done" again, those previously selected (now unselected) apps remain blocked and still show the shield.
We have developed a Parental/Self control app using Screen time API.
We have used individual authentication to authorize the app, using the instructions here:
https://developer.apple.com/documentation/familycontrols/authorizationcenter
The problem is , that individual auth can be disabled easily , by the following steps:
enter Settings app.
in Settings app, click on the Parental/Self control app.
click to disable screen time restriction.
show the device owner's face/fingerprint. (or pin code)
Why is that a problem:
Parental control apps, or self-control apps, are about giving control to the software, To make it hard for the user to disable the restrictions.
So using the flow I have introduced above, it's super-easy for a user to disable his Parental control restrictions, which misses the entire point of Parental/Self control idea.
Furthermore, not only the user have the means to unlock his screen time restrictions, he also MUST have the means to unlock it.
This makes Screen time (with individual auth) useless:
I have a code ready to make a great parental control app for my clients, with amazing ideas, but I can't use the Screen time API unless this problem is fixed.
Why child-parent auth is not enough:
My clients are grownups people between ages of 15-40, that are interested in self-control, so they don't have iCloud child accounts.
also, the child-parent auth solution forces my clients to give some control to other person, and my clients prefer their privacy. Some of them prefer self-control and not parental-control.
What I suggest as a solution:
1: Give more options to users how to disable the Screen time restrictions. including:
a second faceID / FingerPrint (that isn't the same as the one used to unlock the device)
a second pin password.
a string password
2: Give the users the option to choose to not have the device's owner Face/Finger/Pincode ID , as a method to disable the Screen time restrictions.
Hello,
The purpose of "Screen Time Passcode" under Settings/Screen Time is to protect Screen Time preferences and it is asked every time the user updates Downtime, App Limits, Content & Privacy Restrictions and so on.
But the private passcode is not requested if the user disables Screen Time for a particular app (only Face ID or phone passcode is requested, but not the private Screen Time passcode).
I think this is a mistake, I think the purpose of a private Screen Time passcode is to protect all settings, including apps that use this API, right?
Is there any solution to this?
Thank you.
Hi all,
I'm working on a Screen Time-based app with gamification features for families, where both children and parents interact and compare usage stats. The endgoal of the app is to motivate for less screentime or more use of productive apps. I'm testing Apple's Family Controls API, which works great for getting data from child devices. However, this API doesn't support fetching screen time data on the parent device itself.
Apps like Jomo appear to provide insights and even their own "calculations" on screen time usage directly on the parent device. I have tested a few apps that showed both the usage data for my children and myself and did some nice things with it like creating stats. I've gone through the documentation and APIs extensively, but I can’t figure out how they’re doing this.
As far as I can tell the only solution would be a custom VPN or MDM. However as far as I can tell Jomo for example does not use either of those.
My questions are:
Am I missing some API
Are apps like Jomo using private APIs which they have been granted access to from Apple?
Is there any way to access similar data on a parent device without using MDM or VPN?
Any guidance or clarification would be greatly appreciated!
So I have been working with the screen time api. however I still cant get it to work to reshield certain apps after a certain time because for example Dispatch Queue just gets terminated after a certain time.
This is my code right now but the reshielding doesn't get called. Please help I have been working on this since weeks and weeks.
import ManagedSettings
import DeviceActivity
import Foundation
class ShieldActionExtension: ShieldActionDelegate {
let store = ManagedSettingsStore()
let center = DeviceActivityCenter()
override func handle(action: ShieldAction, for application: ApplicationToken, completionHandler: @escaping (ShieldActionResponse) -> Void) {
switch action {
case .primaryButtonPressed:
// Unshield the app
store.shield.applications?.remove(application)
// Encode and persist ApplicationToken
if let encoded = try? PropertyListEncoder().encode([application]) {
UserDefaults(suiteName: "group.Organization.BrainRipe.cmonnow")?.set(encoded, forKey: "StoredApplicationTokens")
}
let unshieldDurationMinutes = 2
let now = Date()
guard let endDate = Calendar.current.date(byAdding: .minute, value: unshieldDurationMinutes, to: now) else {
completionHandler(.close)
return
}
let activityName = DeviceActivityName("com.myapp.shield.reapply")
let schedule = DeviceActivitySchedule(
intervalStart: Calendar.current.dateComponents([.hour, .minute], from: now),
intervalEnd: Calendar.current.dateComponents([.hour, .minute], from: endDate),
repeats: false
)
do {
try center.startMonitoring(activityName, during: schedule)
} catch {
print("Error starting monitoring: \(error)")
}
completionHandler(.close)
case .secondaryButtonPressed:
completionHandler(.defer)
@unknown default:
fatalError("Unhandled ShieldAction case.")
}
}
}
import DeviceActivity
import ManagedSettings
import Foundation
// Optionally override any of the functions below.
// Make sure that your class name matches the NSExtensionPrincipalClass in your Info.plist.
class DeviceActivityMonitorExtension: DeviceActivityMonitor {
let store = ManagedSettingsStore()
override func intervalDidStart(for activity: DeviceActivityName) {
super.intervalDidStart(for: activity)
// Handle the start of the interval.
}
override func intervalDidEnd(for activity: DeviceActivityName) {
guard let data = UserDefaults(suiteName: "group.Organization.BrainRipe.cmonnow")?.data(forKey: "StoredApplicationTokens"),
let tokens = try? PropertyListDecoder().decode([ApplicationToken].self, from: data) else {
return
}
let tokenSet = Set(tokens)
if store.shield.applications == nil {
store.shield.applications = tokenSet
} else {
store.shield.applications?.formUnion(tokenSet)
}
// Clear tokens after use
UserDefaults(suiteName: "group.Organization.BrainRipe.cmonnow")?.removeObject(forKey: "StoredApplicationTokens")
}
}
Topic:
App & System Services
SubTopic:
General
Tags:
SwiftUI
Family Controls
Managed Settings
Screen Time
Hi all,
I’ve run into a frustrating issue with the FamilyControls and DeviceActivityMonitor APIs.
I’ve received official approval from Apple to use the com.apple.developer.family-controls entitlement (distribution), and I’ve added the entitlement to both my main app and the DeviceActivityMonitor extension. I’ve also ensured the correct App Group is configured for both targets.
Everything works perfectly when I install the app on my own device as an internal TestFlight tester. App blocking works, the DeviceActivityMonitor extension runs as expected, and the apps selected by the user are correctly shielded.
However, for external TestFlight testers, while they do receive the Screen Time permission prompt, and can select apps to block, nothing actually gets blocked. It appears that the DeviceActivityMonitor extension is not being triggered at all on their devices.
I’ve verified the following:
The entitlement is approved and visible in App Store Connect
The build is approved for external testing
Testers are running iOS 16+
Shielding logic works properly on internal tester devices
Clean installs have been tested on external devices
Has anyone gotten FamilyControls + DeviceActivityMonitor working successfully for external testers via TestFlight?
If this is a known limitation or if there are any additional steps required to enable extension execution for external users, I’d really appreciate any clarification.
Thanks in advance for your help.
Topic:
App Store Distribution & Marketing
SubTopic:
TestFlight
Tags:
TestFlight
Testing
Family Controls
Hi,
I want to track users screen time on websites.
I want to track monitoring of specific website for that user will enter URL , we want to start monitoring and shield after threshold reached.
but DeviceActivityEvent doesn't allow to enter specific URL domain for monitoring .
How should I do monitoring then ?
Topic:
Developer Tools & Services
SubTopic:
Apple Developer Program
Tags:
Family Controls
Screen Time
I'm working with the FamilyControls and DeviceActivity frameworks in iOS (Swift).
In my app, I collect selected apps using a FamilyActivitySelection, and I access the selected apps via selection.applicationTokens, which gives me a Set.
I would like to get either the bundle identifier or the display name of the selected apps from these ApplicationTokens.
I tried creating an Application instance using:
let app = Application(token: token)
print(app.bundleIdentifier)
print(app.localizedDisplayName)
However, both bundleIdentifier and localizedDisplayName are always nil.
My questions are:
Outside the extension (in the main app), how can I get the bundleIdentifier or display name from an ApplicationToken?
Is there an Apple-recommended way to resolve a Token into something human-readable or usable?
If not, what is the best practice to store or identify user-selected apps for later use?
Environment:
iOS 17,
Swift 5,
Using FamilyControls and DeviceActivity APIs.
Thank you for any help!
I've been working a lot with the FamilyControls API and App Shield recently but have encountered a problem with no documentation. I used the FamilyActivitySelection to select the app store to shield(This is just for testing), and then printed out the application token:
1wfY¸êB ò S« öi #×(É?âðw ù/jQ ¿ J ïE¢? ·¿ º<Òd?ý r7¥Ãn N átJ¹ÿ85B_{VAF fC8. ,,¸¯3 T7F ±õü; ¹?v@¯ô Ä \-õ# Ò
I know the application token is a Codable object so I was wondering,
How do I create an application token using the Token<Application> initializer
init(from: any Decoder) throws
Creates a new instance by decoding from the given decoder.
Using the above data? Do I have to encode first in order to decode it?
For reference, the code I tried to use is:
newValue.applicationTokens.encode(to: JSONEncoder)
if let encoded = try? JSONEncoder().encode(newValue.applicationTokens) {
data = encoded
print(String(data: data, encoding: .utf8)!)
}
if let app = try? JSONDecoder().decode(Token<Application>.self, from: data) {
let token = Application(token: app)
print(token)
} else {
print("didn't work")
}
But it prints didn't work every time.
What should I do differently?
Topic:
App & System Services
SubTopic:
General
Tags:
Privacy
Application Services
Managed Settings
Family Controls
Hi everyone,
I'm working on an app for parents and kids where parents can define screen time goals or restrict usage of certain app categories (like social media or games). If the kid follows those rules—for example, by using their device less or avoiding restricted categories—they would earn points or rewards in the app.
I’ve been exploring if the Apple Screen Time API allows developers to access this kind of data (like total screen time, app usage by category, etc.) so that I can track the kid’s behavior and reward them accordingly.
Is it possible to programmatically access this data and implement such a reward system within my app? If so, what’s the best way to get started or which APIs should I look into?
Thanks in advance for your help!
All
After about 20 hours straight of working on this and having scrapped it twice I am realizing I should have asked everyone here for help.
I am just trying to get device activity report extension to work inside an existing app.
I have been heavily using family controls, managedsettings and deviceactivity and decided it would be nice to output some of the app usage so the User (parent) can see their children's app usage.
I installed the target via xcode, confirmed group names match, and think I have it embedded correctly but when I run the app and call the view within the extension to show minutes used by any apps it just shows no time has been used. In addition, when I put print statements into the extension they do not show up in console.
I have confirmed the main app target->Build phases->Link binary with Libraries has:
ManagedSettings.framework
FamilyControls.Framework
DeviceActivity.framework
I have confirmed in xcode that the main app target->Build phases -> Embed Foundation Extensions has:
ShieldConfiguration.appex
ShieldActionExtension.appex
DeviceActivityMonitor.appex
I have confirmed in xcode that the main app target->Build phases-> Embed ExtensionKit Extensions has:
UsageReportExtension.appex
I have used the apps I am trying to show data for extensively in the last 36 hours.
Here is my UsageReportExtension info.plist
EXAppExtensionAttributes
EXExtensionPointIdentifier
com.apple.deviceactivityui.report-extension
.entitlement
com.apple.developer.family-controls
com.apple.security.application-groups
group.com.jrp.EarnYourTurnMVP2.data
Here is the file in the app (timebankview.swift) calling the extension/showing the extension view(AppUsageReportView.swift)
import DeviceActivity
import ManagedSettings
struct TimeBankView: View {
@EnvironmentObject private var appState: AppState
@State private var reportInterval: DateInterval = {
let calendar = Calendar.current
let now = Date()
let yesterdayDate = calendar.date(byAdding: .day, value: -1, to: now) ?? now
return DateInterval(start: yesterdayDate, end: now)
}()
private var reportFilter: DeviceActivityFilter {
let selection = appState.screenTimeController.currentSelection
return DeviceActivityFilter(
segment: .daily(during: reportInterval),
users: .children,
devices: .all,
applications: selection.applicationTokens,
categories: selection.categoryTokens
// webDomains: selection.webDomains // Add if needed
)
}
var body: some View {
ZStack {
Color.appTheme.background(for: appState.isParentMode)
.edgesIgnoringSafeArea(.all)
ScrollView {
VStack(spacing: 20) {
Text("Time Bank")
DeviceActivityReport(.childUsageSummary, filter: reportFilter)
Here is AppUsageReportView.swift
import SwiftUI
struct AppUsageReportView: View {
let config: DetailedAppUsageConfiguration // Use the detailed config
var body: some View {
VStack {
Text("App Usage Details")
Text("Total Screen Time: \(config.totalDurationFormatted)")
if config.applicationsUsed.isEmpty {
Text("No specific app usage data available for the selected period/filter.")
} else {
Text("Apps Used:")
List {
ForEach(config.applicationsUsed) { appInfo in
HStack {
Image(systemName: "app.dashed")
Text(appInfo.appName)
.lineLimit(1)
Text(appInfo.durationFormatted)
Here is AppUsageReportScene.swift:
import SwiftUI
import ManagedSettings
struct AppInfo: Identifiable, Hashable {
let id = UUID()
let appName: String
let durationFormatted: String
}
struct DetailedAppUsageConfiguration {
var totalDurationFormatted: String = "Calculating..."
var applicationsUsed: [AppInfo] = []
}
struct AppUsageReportScene: DeviceActivityReportScene {
let context: DeviceActivityReport.Context = .childUsageSummary
let content: (DetailedAppUsageConfiguration) -> AppUsageReportView
func makeConfiguration(representing data: DeviceActivityResults<DeviceActivityData>) async -> DetailedAppUsageConfiguration {
var config = DetailedAppUsageConfiguration()
var appDurations: [String: TimeInterval] = [:]
var totalAggregatedDuration: TimeInterval = 0
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = .abbreviated
formatter.zeroFormattingBehavior = .pad
var segmentCount = 0
var categoryCount = 0
var appCount = 0
for await activityData in data {
// Check segments
var tempSegmentCount = 0
for await segment in activityData.activitySegments {
segmentCount += 1
tempSegmentCount += 1
totalAggregatedDuration += segment.totalActivityDuration
var tempCategoryCount = 0
for await categoryActivity in segment.categories {
categoryCount += 1
tempCategoryCount += 1
var tempAppCount = 0
for await appActivity in categoryActivity.applications {
appCount += 1
tempAppCount += 1
let appName = appActivity.application.localizedDisplayName ?? "Unknown App"
let duration = appActivity.totalActivityDuration
appDurations[appName, default: 0] += duration
}}} }
config.totalDurationFormatted = formatter.string(from: totalAggregatedDuration) ?? "N/A"
config.applicationsUsed = appDurations
.filter { $0.value >= 1
.map { AppInfo(appName: $0.key, durationFormatted: formatter.string(from: $0.value) ?? "-") }
.sorted { lhs, rhs in
let durationLHS = appDurations[lhs.appName] ?? 0
let durationRHS = appDurations[rhs.appName] ?? 0
return durationLHS > durationRHS
}
if !config.applicationsUsed.isEmpty {
for (index, app) in config.applicationsUsed.enumerated() {
}
} else {
}
return config
}}
UsageReportExtension.swift
struct UsageReportExtension: DeviceActivityReportExtension {
init() {
print("🚀 [UsageReportExtension] Extension initialized at \(Date())")
print("🔍 [UsageReportExtension] Process info: \(ProcessInfo.processInfo.processName) PID: \(ProcessInfo.processInfo.processIdentifier)")
}
var body: some DeviceActivityReportScene {
let _ = print("📊 [UsageReportExtension] Building report scenes at \(Date())")
TotalActivityReport { totalActivity in
print("🕰️ [TotalActivityReport] Creating view with data: \(totalActivity)")
return TotalActivityView(totalActivity: totalActivity)
}}}
Topic:
App & System Services
SubTopic:
General
Tags:
Device Activity
Family Controls
Managed Settings
Screen Time
.fullScreenCover(isPresented: $isShow) {
Button(action : {
isPresented.toggle()
}){
Text("Choose")
}
.familyActivityPicker(
isPresented: $isPresented,
selection: $selection)
.onChange(of: selection){ value in
}
}
In the official version of iOS 18, when I click the Done button of familyActivityPicker, the fullScreenCover pop-up box will disappear.
Hello Apple Developer Community,
I have an existing Family Controls entitlement approved for my main app. My app includes the following extensions:
AppLimitMonitorExtension
ShieldConfigurationExtension
ShieldActionExtension
I am seeking clarification on whether my existing Family Controls entitlement for the main app automatically covers these extensions, or if I must submit separate entitlement requests for each extension. Because when I try to distribute app on App Store Connect it give me error "Provisioning profile failed qualification".
Many developers appear to be facing this issue, and it seems that Apple has not clearly documented this.
is information anywhere.
Topic:
App Store Distribution & Marketing
SubTopic:
General
Tags:
Extensions
Family Controls
Entitlements